Import Module in ES6 Class Function - node.js

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

Related

ESM import a .node addon

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

How to use Winston Logger in NestJS separate module that doesn't use Classes

I've tried a few different ways of doing this.
I can't set Winston as the default logger for NestJS at the moment because it complains about "getTimestamp" function not being in the instance.
So - for controllers in NestJS - I have used dependency injection - which works fine for the api ( REST endpoints ).
The problem is that I have moved away from OOP - so all of my libraries are written in typescript as functions. Not pure functions but better than an OOP approach ( many less bugs! )
My question is - how do I get access to the main winston logger within my libraries that don't have classes.
I am using the library nest-winston.
Have you tried this?
create the logger outside of the application lifecycle, using the createLogger function, and pass it to NestFactory.create (nest-winston docs)
You can have a separate file that creates the logging instance, then import that into your modules/libraries as well as import it into your main.ts
// src/logger/index.ts
import { WinstonModule } from 'nest-winston';
export const myLogger = WinstonModule.createLogger({
// options (same as WinstonModule.forRoot() options)
})
// src/myLib/index.ts
import { myLogger } from '#/logger' // I like using aliases
export const myLib = () => {
// ...
myLogger.log('Yay')
}
// src/main.ts
import { myLogger } from '#/logger'
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: myLogger
});
}
bootstrap();

How to correctly add and use Objection.js

I am trying to add Objection.js to my project (using ES6 "type": "module"), and getting this error which points to ./models/user.js:
import { Model } from "objection";
^^^^^
SyntaxError: The requested module 'objection' does not provide an export named 'Model'
Using the following code:
./methods.js
import User from "./models/user.js";
async function getInfo(idnum) {
const someUser = await User.query().findById(idnum);
return someUser;
}
./models/user.js
import db from "../connection.js";
import { Model } from "objection";
Model.knex(db);
class User extends Model {
static get tableName() {
return "users";
}
}
export default User;
./connection.js
const environment = process.env.NODE_ENV || "development";
import knexfile from "../knexfile.js";
const connection = knexfile[environment];
import knex from "knex";
const db = knex(connection);
export default db;
UPDATE
The creator of Objection.js said import { Model } from "objection" should work.
What am I doing wrong?
The only current workaround appears to be importing the Model like this:
import objection from "objection";
const { Model } = objection;
since Objection.js does exports like this:
export default { Model }
and not like this:
export { Model }
I hope that you are using a .mjs extension for the file if you are using import in a Node app.
But if you are using a .js as extension then you have to call that module using require.
const { Model } = require('objection');
This was the problem I once had... I don't know if this is the solution to your problem.

Is there a way to mock `module` variable?

I want to test my app init function, that depends on node.js module variable (webpack's module variable to be precise). I can't pass module variable as a function parameter because of this webpack GitHub issue.
I found this rewire library, that seems to be good for my problem. But it looks like it is abandoned and has some errors because of my ES6 imports.
Code sample I want to test:
import ReactDOM from 'react-dom'
import React from 'react'
import Root from './components/Root'
export function init() {
const {AppContainer} = require('react-hot-loader')
const render = Component => {
ReactDOM.render(
<AppContainer>
<Component />
</AppContainer>,
document.getElementById('root')
)
}
render(Root)
if (module.hot) { // the part I would like to test by mocking `module` variable
module.hot.accept('./components/Root', () => { render(Root) })
}
}
init()

module.exports vs. export default in Node.js and ES6

What is the difference between Node's module.exports and ES6's export default? I'm trying to figure out why I get the "__ is not a constructor" error when I try to export default in Node.js 6.2.2.
What works
'use strict'
class SlimShady {
constructor(options) {
this._options = options
}
sayName() {
return 'My name is Slim Shady.'
}
}
// This works
module.exports = SlimShady
What doesn't work
'use strict'
class SlimShady {
constructor(options) {
this._options = options
}
sayName() {
return 'My name is Slim Shady.'
}
}
// This will cause the "SlimShady is not a constructor" error
// if in another file I try `let marshall = new SlimShady()`
export default SlimShady
The issue is with
how ES6 modules are emulated in CommonJS
how you import the module
ES6 to CommonJS
At the time of writing this, no environment supports ES6 modules natively. When using them in Node.js you need to use something like Babel to convert the modules to CommonJS. But how exactly does that happen?
Many people consider module.exports = ... to be equivalent to export default ... and exports.foo ... to be equivalent to export const foo = .... That's not quite true though, or at least not how Babel does it.
ES6 default exports are actually also named exports, except that default is a "reserved" name and there is special syntax support for it. Lets have a look how Babel compiles named and default exports:
// input
export const foo = 42;
export default 21;
// output
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var foo = exports.foo = 42;
exports.default = 21;
Here we can see that the default export becomes a property on the exports object, just like foo.
Import the module
We can import the module in two ways: Either using CommonJS or using ES6 import syntax.
Your issue: I believe you are doing something like:
var bar = require('./input');
new bar();
expecting that bar is assigned the value of the default export. But as we can see in the example above, the default export is assigned to the default property!
So in order to access the default export we actually have to do
var bar = require('./input').default;
If we use ES6 module syntax, namely
import bar from './input';
console.log(bar);
Babel will transform it to
'use strict';
var _input = require('./input');
var _input2 = _interopRequireDefault(_input);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
console.log(_input2.default);
You can see that every access to bar is converted to access .default.
Felix Kling did a great comparison on those two, for anyone wondering how to do an export default alongside named exports with module.exports in nodejs
module.exports = new DAO()
module.exports.initDAO = initDAO // append other functions as named export
// now you have
let DAO = require('_/helpers/DAO');
// DAO by default is exported class or function
DAO.initDAO()
You need to configure babel correctly in your project to use export default and export const foo
npm install --save-dev #babel/plugin-proposal-export-default-from
then add below configration in .babelrc
"plugins": [
"#babel/plugin-proposal-export-default-from"
]

Resources