ESM import a .node addon - node.js

I am trying to import a .node binary addon in an ESM & Node Typescript based context. However, when I try to do this I get the following error "error TS2307: Cannot find module './addon.node' or its corresponding type declarations."
I've looked online for several solutions, these are my versions:
NodeJS: v16.14.1
ts-node: v10.7.0
Typescript: 4.6.3
This is my current approach for importing:
import addon from "./addon.node";
Just to note, because of my configuration I am limited to only using import.
Thanks in advance for any support.

Node.js import doesn’t support .node files. To import such files in an ESM context, you need to use createRequire:
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const addon = require('./addon.node');
You could also import the .node file in a CommonJS file that an ESM file then imports.
// addon.cjs
module.exports = require('./addon.node');
// main.js
import addon from './addon.cjs';
Finally, you could create an ESM loader that adds support for .node files to import, by wrapping the createRequire method into a loader (untested):
import { cwd } from 'node:process';
import { pathToFileURL } from 'node:url';
const baseURL = pathToFileURL(`${cwd()}/`).href;
export async function resolve(specifier, context, nextResolve) {
if (specifier.endsWith('.node')) {
const { parentURL = baseURL } = context;
// Node.js normally errors on unknown file extensions, so return a URL for
// specifiers ending in `.node`.
return {
shortCircuit: true,
url: new URL(specifier, parentURL).href,
};
}
// Let Node.js handle all other specifiers.
return nextResolve(specifier);
}
export async function load(url, context, nextLoad) {
if (url.endsWith('.node')) {
const source = `
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
const require = createRequire(import.meta.url);
const path = fileURLToPath(${url});
export default require(path);`;
return {
format: 'module',
shortCircuit: true,
source,
};
}
// Let Node.js handle all other URLs.
return nextLoad(url);
}

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.

Jest cannot find module firebase-admin/storage imported in package

I have a package admin-sdk which is used in multiple projects, has a dependency on firebase-admin and exports a function export function storage(): admin.storage.Storage to get a reference to cloud storage.
This package is used in a nestJS app and works fine but when I run tests with Jest I get module not found error:
● Test suite failed to run
Cannot find module 'firebase-admin/storage' from 'node_modules/admin-sdk/dist/firebase/storage.js'
Require stack:
node_modules/admin-sdk/dist/firebase/storage.js
node_modules/admin-sdk/dist/firebase/index.js
node_modules/admin-sdk/dist/index.js
src/services/provocation/provocation.service.ts
src/services/provocation/provocation.service.spec.ts
at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:306:11)
at Object.<anonymous> (node_modules/admin-sdk/dist/firebase/storage.js:4:19)
node_modules/admin-sdk/storage.ts
import {Bucket} from "#google-cloud/storage";
import {getStorage, Storage} from "firebase-admin/storage";
let _storage: Storage;
const _buckets: Record<string, Bucket> = {};
export function storage(): Storage {
if (!_storage) {
_storage = getStorage(); //admin.storage();
}
return _storage;
}
export function bucket(name?: string): Bucket {
let id = name || "";
if (!_buckets.hasOwnProperty(id)) {
_buckets[id] = storage().bucket(name);
}
return _buckets[id];
}
Both libraries depend on same version of firebase-admin which is ^10.0.2.
So why is this error occuring and how can I fix it?
Try mapped this manually on jest.config.ts:
import { pathsToModuleNameMapper } from 'ts-jest/utils';
import { compilerOptions } from './tsconfig.json';
...
export default {
...
moduleNameMapper: pathsToModuleNameMapper(
{
...compilerOptions.paths,
'firebase-admin/*': ['node_modules/firebase-admin/lib/*'],
},
{
prefix: '<rootDir>',
},
),
...
}

Why I can not use " import { createSlice, configureStore } from '#reduxjs/toolkit' " in Node.js

guys
I am learning redux, and try to run a very simple example code in node.js environment. I got the following error when I try to use :
import { createSlice, configureStore } from '#reduxjs/toolkit' .
The errors is:
import { createSlice, configureStore } from '#reduxjs/toolkit'
^^^^^^^^^^^
SyntaxError: Named export 'createSlice' not found. The requested module '#reduxjs/toolkit' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from '#reduxjs/toolkit';
const { createSlice, configureStore } = pkg;
at ModuleJob._instantiate (internal/modules/esm/module_job.js:120:21)
at async ModuleJob.run (internal/modules/esm/module_job.js:165:5)
at async Loader.import (internal/modules/esm/loader.js:177:24)
at async Object.loadESM (internal/process/esm_loader.js:68:5)
If I use import like what the error tip says:
import pkg from '#reduxjs/toolkit';
const { createSlice, configureStore } = pkg;
All is OK.
What I want to ask is:
It gives me a wrong example in the official website of Redux? Or Just I run the example with a wrong way?
The following is the detail information.
My Node.js version is: v14.17.3
1 Init a node project:
mkdir redux_01
cd redux_01
yarn init
yarn add #reduxjs/toolkit
2 Modify the 'package.json', add a line in it:
"type":"module"
3 Create a file 'index.js' with the "Redux Toolkit Example" code parsed from https://redux.js.org/introduction/getting-started.
import { createSlice, configureStore } from '#reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
incremented: state => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1
},
decremented: state => {
state.value -= 1
}
}
})
export const { incremented, decremented } = counterSlice.actions
const store = configureStore({
reducer: counterSlice.reducer
})
// Can still subscribe to the store
store.subscribe(() => console.log(store.getState()))
// Still pass action objects to `dispatch`, but they're created for us
store.dispatch(incremented())
// {value: 1}
store.dispatch(incremented())
// {value: 2}
store.dispatch(decremented())
// {value: 1}
4 Now I run it like this:
node index.js
I then got that error message that I just mentioned.
The reason for the error is explained here:
https://lightrun.com/answers/reduxjs-redux-toolkit-cannot-import-redux-toolkit-from-a-nodejs-esm-module "here"
solution:
import * as toolkitRaw from '#reduxjs/toolkit';
const { createSlice,configureStore } = toolkitRaw.default ?? toolkitRaw;
or in Typescript:
import * as toolkitRaw from '#reduxjs/toolkit';

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

Import Module in ES6 Class Function

I have migrated my project to ESM and thus using .mjs in all my files in nodejs.
Previously in CommonJs, I could require a file right in the middle of a ES6 class function in order to load it only when needed.
module.exports = class Core{
constructor() {
this.init = this._init.bind(this)
return this.init()
}
async _init(){
const module = require('module')
//use required file/module here
}
}
But now when using Michael Jackson Scripts a.k.a .mjs, I cannot import a file on demand:
import Koa from 'koa'
export default = class Core{
constructor() {
this.init = this._init.bind(this)
return this.init()
}
async _init(){
import module from 'module'
//use imported file/module here
}
}
My app has many files/modules that are not consumed immediately, and can more can always be added in future, thus hardcoding the imports at the begining of the file is not an option.
Is there a way to import the files dynamically on demand when needed?
With a little modification from this answer, I managed to get it working via:
import Koa from 'koa'
export default = class Core{
constructor() {
this.init = this._init.bind(this)
return this.init()
}
async _init(){
const router = await import('./Router')
//use imported file/module here
}
}
Or you can use a promise if you are into that:
import('./router')
.then(something => {
//use imported module here
});
This suits me for now until the spec if finalised and shipped

Resources