NodeJS JavaScript project with JS decorators (stage 3) and Babel doesn't work

717 views Asked by At

I am trying to use JavaScript decorators (stage 3 proposal) in my NodeJS project, which is written in JavaScript. However, I am having trouble getting this to work with nodemon and babel together, so that the code will be recompiled when it is changed.

I tried two different package.json configurations:

First one:

"scripts": {
    "build": "babel src -d dist",
    "start": "npm run build && nodemon dist/app.js"
  },

It works the first time only, when I made a change in code it recompiles but I see that runtime is not updated, I see the old result.

Second configuration:

"scripts": {
    "start-nodemon": "nodemon --exec babel-node src/app.js"
  },

It doesn't work with decorators and I get the following exception:

 @test(true)
    ^

SyntaxError: Invalid or unexpected token

When I remove the decorator it works.

My .babelrc config is following:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "modules": false
      }
    ]
  ],
  "env": {
    "development": {
      "sourceMaps": "inline",
      "retainLines": true
    }
  },
  "plugins": [
    "@babel/plugin-syntax-top-level-await",
    ["@babel/plugin-proposal-decorators", {
      "version": "2022-03"
    }]
  ]
}

app.js file

import MyModule from "./module.js";

const initializedModule = new MyModule();
const description = initializedModule.getDescription();
console.log(description);

decorators.js file

export function test(test){
    return function (value, { kind, name }) {
        if (kind === "method" || kind === "getter" || kind === "setter") {
            return function (...args) {
                console.log(test);
                console.log(`starting ${name} with arguments ${args.join(", ")}`);
                const ret = value.call(this, ...args);
                console.log(`ending ${name}`);
                return ret;
            };
        }
    }
}

and module.js file:

import {test} from "./decorators.js";

export default class MyModule {
    @test(true)
    getDescription () {
        return "MyModule Test12";
    }
}

How can I properly configure my project, so code will be recompiled and rerun upon changes?

GitHub repository with my project: babel-test

Thank you!

1

There are 1 answers

3
Mauro Stepanoski On BEST ANSWER

Try removing @babel/node and replacing it with babel-register-esm:

npm uninstall @babel/core && npm install --save-dev babel-register-esm

Then modify the package.json file, the scripts section:

...
"scripts": {
      "build": "babel src -d dist",
      "start": "yarn build && node --experimental-specifier-resolution=node dist/app.js",
      "start-nodemon": "nodemon --watch \"./src/**/*.js\" --ignore \"node_modules\" --exec \"node --loader babel-register-esm src/app.js\""
   },
...

This part

node --loader babel-register-esm src/app.js

is the most important. It changes the node loader to one that can process ESM features.

And this part:

node --loader babel-register-esm src/app.js

makes it possible to start a the ESM files.

The expected result is:

starting getDescription with arguments 
ending getDescription
MyModule Test12

I used yarn instead of npm, and my package.json is:

{
 "name": "fixstack",
 "version": "1.0.0",
 "main": "dist/app.js",
 "license": "MIT",
 "type": "module",
 "scripts": {
   "build": "babel src -d dist",
   "start": "yarn build && node --experimental-specifier-resolution=node dist/app.js",
   "start-nodemon": "nodemon --watch \"./src/**/*.js\"  --exec \"node --loader babel-register-esm src/app.js\""
 },
   "dependencies": {},
   "devDependencies": {
      "@babel/cli": "^7.20.7",
      "@babel/core": "^7.20.12",
      "@babel/plugin-proposal-decorators": "^7.20.7",
      "@babel/preset-env": "^7.20.2",
      "babel-register-esm": "^1.2.4",
      "nodemon": "^2.0.20"
    }
 }

If you don't want to use babel-register-esm and want to call "start", you need to modify the scripts section:

  "scripts": {
      "build": "babel src -d dist",
      "start": "yarn build && node --experimental-specifier-resolution=node dist/app.js",
      "start-nodemon": "nodemon --watch \"./src/**/*.js\"  --exec \"yarn start\""
  },

Or if you are using NPM instead of YARN:

  "scripts": {
      "build": "babel src -d dist",
      "start": "npm run build && node --experimental-specifier-resolution=node dist/app.js",
      "start-nodemon": "nodemon --watch \"./src/**/*.js\" --exec \"npm run start\""
  },

Why "--experimental-specifier-resolution=node"?

If you are using type module in your package.json and implementing using import and export instead of CommonJS style, then you need to use this flag (except you use an explicit path on your imports, you need to add the .js extension to the filename; i.e: import someFunc from './dir/someFile.js'). This flag was added in: Node v13.4.0, Node v12.16.0, required until Node v18 (current LTS) at least.

You could get more information here