So I've been using ES6 style syntax with import/export on Nodejs with the ESM module loader. Everything has been fine until I started getting an error pertaining to imports.
Here's the error messages:
joseph@InsaneMachine:~/placeholder2/main-server$ npm start
> [email protected] start /home/joseph/placeholder2/main-server
> nodemon --experimental-modules src/index.mjs
[nodemon] 1.19.4
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node --experimental-modules src/index.mjs`
(node:16942) ExperimentalWarning: The ESM module loader is experimental.
file:///home/joseph/placeholder2/main-server/src/games/game-player.mjs:3
export default class GamePlayer extends Player
^
ReferenceError: Cannot access 'Player' before initialization
at file:///home/joseph/placeholder2/main-server/src/games/game-player.mjs:3:41
at ModuleJob.run (internal/modules/esm/module_job.js:109:37)
at async Loader.import (internal/modules/esm/loader.js:132:24)
[nodemon] app crashed - waiting for file changes before starting...
Here are the files Player (Base class):
import PasswordHash from 'password-hash';
import GamesService from '../games/games.service.mjs';
import PlayersService from './players.service.mjs';
import QueueingService from '../queueing/queueing.service.mjs';
export default class Player
{
constructor(object)
{
Object.assign(this, JSON.parse(JSON.stringify(object)));
}
get id()
{
return this._id.toString();
}
equals(other)
{
if(other.id != null)
return other.id == this.id;
return false;
}
checkPassword(password)
{
return PasswordHash.verify(password, this.password);
}
online()
{
return PlayersService.consumer.isPlayerOnline(this);
}
inQueue()
{
return QueueingService.queued(this);
}
inGame()
{
return GamesService.getActiveGameByPlayer(this) != null;
}
reduce()
{
return {
id: this.id,
username: this.username,
email: this.email,
admin: this.admin,
online: this.online(),
in_queue: this.inQueue(),
in_game: this.inGame(),
};
}
static hashPassword(password)
{
return PasswordHash.generate(password);
}
static schema = {
username: String,
password: String,
email: String,
email_confirmed: Boolean,
admin: Boolean,
}
}
And GamePlayer (Child Class):
import Player from '../players/player.mjs';
export default class GamePlayer extends Player
{
constructor(player, token)
{
super(player);
this.token = token;
}
}
And the heirarchy of the project:
src/
-- games/
-- -- game-player.mjs
-- -- ...
players/
-- -- player.mjs
-- -- ...
-- ...
How can I fix this import issue, unless this is something else?
Edit: I am not using Babel as far as I know, I am using --external-modules provided by Node. Not sure how that works.
The "Cannot access before initialization" error occurs when a variable declared using let or const is accessed before it was initialized in the scope. To solve the error, make sure to initialize the variable before accessing it.
The JavaScript exception "can't access lexical declaration `variable' before initialization" occurs when a lexical variable was accessed before it was initialized. This happens within any block statement, when let or const variables are accessed before the line in which they are declared is executed.
I went to the Node.JS forums and asked what could be the issue. Not a babel issue at all, just circular dependencies. For example:
// A.js
import B from './B.js'
export default class A{}
// B.js
import A from './A.js'
export default class B extends A{}
Sorry there wasn't nearly enough information to be able to figure this one out. I got a lot of help on the node.js github and someone looked through my project on github and ended up finding an instance where two modules pointed at each other.
The dependencies in your import
s were probably too difficult to resolve, so it gave up, leaving you with Player
uninitialized at the point where it is needed to define GamePlayer
.
As I mentioned in a comment for another answer, import
can be used in a "circular" way, but Node.js can't always untangle the dependencies.
In my case it had no problem with a class in one file and a subclass of that in another file, where both of them import
each other, and it's hard to say exactly where it got too complicated, but this is a simplified version of what I had that broke it:
// server.js
import Acorn from './acorn.js';
import Branch from './branch.js';
class Server {
...
}
// universe.js
import Acorn from './acorn.js';
import Branch from './branch.js';
import Thing from './thing.js';
export default class Universe {
things(type) {
if (Thing.klass[type]) {
...
}
}
...
}
// acorn.js
import Thing from './thing.js';
export default class Acorn extends Thing {
...
}
// branch.js
import Thing from './thing.js';
export default class Branch extends Thing {
...
}
// thing.js
import Acorn from './acorn.js';
import Branch from './branch.js';
export default class Thing {
static klass(type) {
const klass = {acorn: Acorn, branch: Branch};
...
return klass;
}
constructor(...) {
this.type = this.constructor.name.toLowerCase();
...
}
...
}
I'm using the same code for browsers and server-side Node.js, so I was also transpiling it with Babel, which handled everything fine. But Node may have other constraints that make it more difficult, because, at least as far as importing is concerned, it was on a different track from browsers (and others), and is now trying to bridge the gap. And also the knot may be more tangled than it appears to the naked eye.
In the end I ditched the most circular part of my pattern, which was the part where I refer to the Thing subclasses within Thing itself. I extracted that into a "factory-like" pattern, where the factory knows about the Thing subclasses, but the Thing and its subclasses don't need the factory.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With