Search code examples
javascriptnode.jsyeomanyeoman-generator

Cannot require/import while creating yeoman generator


I'm trying to create yeoman generator but I'm unable to get started. I'm following this video on the subject and I can't even get my code to run.

here's my package.json file:

{
  "name": "generator-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "yeoman-generator": "^7.1.0"
  }
}

and my index.js file:

const Generator = require("yeoman-generator");

module.exports = class extends Generator {
  initializing() {
    this.log("working!");
  }
};

I can run npm link no issues, and when I try to run it I can see it's been hit, but then I get this error:

require() of ES Module ...\generator-test\node_modules\yeoman-generator\dist\index.js from ...\generator-test\generators\app\index.js not supported.

Instead change the require of ...\generator-test\node_modules\yeoman-generator\dist\index.js in ...\generator-test\generators\app\index.js to a dynamic import() which is available in all CommonJS modules.

so I tried that, and ended up with this index.js:

import Generator from "yeoman-generator";

module.exports = class extends Generator {
  initializing() {
    this.log("working!");
  }
};

and this error:

Cannot use import statement outside a module

I'm sure I'm doing something wrong I just don't what it is. Can some one point me in the right direction?


Solution

  • UPDATE

    Node.js >= v22.0.0 now has experimental support for loading ES modules with require. See Loading ECMAScript modules using require().

    In summary:

    • use --experimental-require-module.
    • The ES module must be fully synchronous, i.e no top-level await.
    • One of the usual methods for determining an ES module is in place:
      • .mjs extension.
      • "type": "module" in package.json.
      • --experimental-detect-module is enabled.

    Original Answer

    First, your selected version of yeoman is published as an ES module which means it can not be consumed with require.

    Second, your project is defined as CJS because your package.json is not defining "type": "module", so defaults to common JS.

    If you want to continue using CJS in your project, then to consume yeoman you need to use a dynamic import() expression which is available in both ESM and CJS contexts. Going this route you should probably use top level await to define your index.js module since dynamic imports use promises. Something along these lines:

    const setup = async () => {
      const Generator = await import('yeoman-generator')
    
      return class extends Generator {
        initializing() {
          this.log("working!");
        }
      }
    }
    
    export default await setup()
    

    The far simpler option is to use ESM in your project by setting "type": "module" in your package.json file and changing your require calls to import.

    import Generator from "yeoman-generator";
    
    export default class extends Generator {
      initializing() {
        this.log("working!")
      }
    }