Ricerca nel sito web

node.js - ERR_REQUIRE_ESM dall'importazione di un pacchetto ESM da parte di webpack


Sto costruendo un'app utilizzando Nx per la modularizzazione e Geckos per la parte server. Con la configurazione predefinita di Webpack 5 di Nx per Node ottengo questo errore quando eseguo l'app:

/Users/hgrzeskowiak/hugo-game/fresh-nx-geckos/nx-geckos-test/dist/apps/geckos-server-app/main.js:8
module.exports = require("@geckos.io/server");
                 ^
Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/hgrzeskowiak/hugo-game/fresh-nx-geckos/nx-geckos-test/node_modules/@geckos.io/server/lib/index.js from /Users/hgrzeskowiak/hugo-game/fresh-nx-geckos/nx-geckos-test/dist/apps/geckos-server-app/main.js not supported.
Instead change the require of index.js in /Users/hgrzeskowiak/hugo-game/fresh-nx-geckos/nx-geckos-test/dist/apps/geckos-server-app/main.js to a dynamic import() which is available in all CommonJS modules.
(...)

Ecco i passaggi per riprodurre:

npx create-nx-workspace@latest nx-geckos-test
Need to install the following packages:
  create-nx-workspace@latest
Ok to proceed? (y) y
✔ What to create in the new workspace · express
✔ Application name                    · geckos-server-app
✔ Use Nx Cloud? (It's free and doesn't require registration.) · No

>  NX  Nx is creating your v13.4.1 workspace.

  To make sure the command works reliably in all environments, and that the preset is applied correctly,
  Nx will run "npm install" several times. Please wait.

✔ Installing dependencies with npm
✔ Nx has successfully created the workspace.
npm run start

> [email  start
> nx serve

> nx run geckos-server-app:serve
chunk (runtime: main) main.js (main) 552 bytes [entry] [rendered]
webpack compiled successfully (26ac6f288b082854)
Debugger listening on ws://localhost:9229/70a1d9b4-af2b-4fc9-90f7-f3502e5fdf2e
Debugger listening on ws://localhost:9229/70a1d9b4-af2b-4fc9-90f7-f3502e5fdf2e
For help, see: https://nodejs.org/en/docs/inspector
Issues checking in progress...
Listening at http://localhost:3333/api

A questo punto, l'app Express predefinita viene costruita e avviata.

Ora sostituisco il contenuto di apps/geckos-server-app/src/main.ts con l'esempio del server Geckos dal README:

import geckos from '@geckos.io/server'

const io = geckos()

io.listen(3000) // default port is 9208

io.onConnection(channel => {
  channel.onDisconnect(() => {
    console.log(`${channel.id} got disconnected`)
  })

  channel.on('chat message', data => {
    console.log(`got ${data} from "chat message"`)
    // emit the "chat message" data to all channels in the same room
    io.room(channel.roomId).emit('chat message', data)
  })
})

...e installare il pacchetto del server geckos usando

npm install @geckos.io/server

added 4 packages, and audited 785 packages in 7s

87 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Ok, è il momento di eseguirlo di nuovo:

npm run start

> [email  start
> nx serve


> nx run geckos-server-app:serve
chunk (runtime: main) main.js (main) 610 bytes [entry] [rendered]
webpack compiled successfully (90920432a9d246ea)
Debugger listening on ws://localhost:9229/dc6d9e1a-1a15-432f-a46c-d2ac83b923ea
Debugger listening on ws://localhost:9229/dc6d9e1a-1a15-432f-a46c-d2ac83b923ea
For help, see: https://nodejs.org/en/docs/inspector

/Users/hgrzeskowiak/hugo-game/fresh-nx-geckos/nx-geckos-test/dist/apps/geckos-server-app/main.js:8
module.exports = require("@geckos.io/server");
                 ^
Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/hgrzeskowiak/hugo-game/fresh-nx-geckos/nx-geckos-test/node_modules/@geckos.io/server/lib/index.js from /Users/hgrzeskowiak/hugo-game/fresh-nx-geckos/nx-geckos-test/dist/apps/geckos-server-app/main.js not supported.
Instead change the require of index.js in /Users/hgrzeskowiak/hugo-game/fresh-nx-geckos/nx-geckos-test/dist/apps/geckos-server-app/main.js to a dynamic import() which is available in all CommonJS modules.
    at [email /server (/Users/hgrzeskowiak/hugo-game/fresh-nx-geckos/nx-geckos-test/dist/apps/geckos-server-app/main.js:8:18)
    at __webpack_require__ (/Users/hgrzeskowiak/hugo-game/fresh-nx-geckos/nx-geckos-test/dist/apps/geckos-server-app/main.js:32:41)
    at /Users/hgrzeskowiak/hugo-game/fresh-nx-geckos/nx-geckos-test/dist/apps/geckos-server-app/main.js:45:18
    at /Users/hgrzeskowiak/hugo-game/fresh-nx-geckos/nx-geckos-test/dist/apps/geckos-server-app/main.js:59:3
    at Object.<anonymous> (/Users/hgrzeskowiak/hugo-game/fresh-nx-geckos/nx-geckos-test/dist/apps/geckos-server-app/main.js:64:12)

Ho testato parecchie modifiche alla configurazione di Webpack e l'utilizzo di type: "module" in package.json, ma senza successo. Il file main.js risultante appare per lo più uguale a prescindere dalle impostazioni di webpack che modifico. Cose che ho provato a cambiare nella configurazione override di webpack dell'app:

module.exports = function(webpackConfig, context) {
  webpackConfig.target = "async-node16";
  webpackConfig.output.libraryTarget = "module";
  webpackConfig.output.library = {type: "module"};
  webpackConfig.output.chunkFormat = "module";
  webpackConfig.experiments.outputModule = true;
  console.log("webpack config:");
  console.log(webpackConfig);
  console.log("module rules:");
  console.log(webpackConfig.module.rules);
  return webpackConfig;
}

I log della console nella configurazione verificano che lo script venga eseguito.

L'app funziona quando modifico manualmente il require in import nell'output trascompilato, ma questa non è una soluzione sostenibile.

Sospetto che l'errore abbia a che fare con il fatto che Geckos è un pacchetto ESM, e Webpack usa solo require invece di import come dovrebbe, ma non sono riuscito a trovare alcun modo per cambiare quel comportamento.

Qualche idea?

  • Versione di Node 16.13.0
  • Versione Nx 13.4.1
  • Webpack (dipendenza transitiva di Nx): 5.65.0

Risposta n. 1

Ecco la soluzione più pulita che sono riuscito a trovare e che funziona con le versioni di TypeScript precedenti alla 4.6 (che dovrebbe aggiungere il supporto per questa funzione). Questo è essenzialmente un modo per inserire il supporto ESM in WebPack con versioni più vecchie di TypeScript (inclusa l’ultima al momento della scrittura).

Un plugin Webpack personalizzato che cambia il tipo del modulo esterno in import.

// TypeScript before version 4.6 does not support transpiling ESM imports to
// `import()`, but uses `require()` instead. NodeJS does not support the use
// of `require() for ECMAScript modules (ESM).
//
// Good reads:
// https://www.typescriptlang.org/docs/handbook/esm-node.html
// https://devblogs.microsoft.com/typescript/announcing-typescript-4-5/#esm-nodejs

/**
 * A Webpack 5 plugin that can be passed a list of packages that are of type
 * ESM. The typescript compiler will then be instructed to use the `import`
 * external type.
 */
class ESMLoader {
  static defaultOptions = {
    esmPackages: "all"
  };

  constructor(options = {}) {
    this.options = { ...ESMLoader.defaultOptions, ...options };
  }

  apply(compiler) {
    compiler.hooks.compilation.tap(
      "ECMAScript Module (ESM) Loader. Turns require() into import()",
      (
        compilation
      ) => {
        compilation.hooks.buildModule.tap("Hello World Plugin", (module) => {
            if (
              module.type === "javascript/dynamic" &&
              (
                this.options.esmPackages === "all" ||
                this.options.esmPackages.includes(module.request)
              )
            ) {
              // All types documented at
              // https://webpack.js.org/configuration/externals/#externalstype
              module.externalType = "import";
            }
          }
        )
        ;
      }
    );
  }
}

module.exports = function(webpackConfig, context) {
  // NodeJS supports dynamic imports since version 12.
  webpackConfig.target = "node16";
  webpackConfig.plugins.push(
    // Manually specify the ESM modules or leave blank for using `import()`
    // on all packages.
    //new ESMLoader()
    new ESMLoader({esmPackages: "@geckos.io/server"})
  );
  return webpackConfig;
};

La configurazione webpack personalizzata può essere impostata su un'app Nx tramite il project.json:

{
  "targets": {
    "build": {
      "executor": "@nrwl/node:build",
      "options": {
       ...
        "webpackConfig": "apps/your-app/webpackConfig.js"
      },
      ...

Questa soluzione richiede anche che tsconfig.json abbia impostato il tipo di modulo su qualcosa di più moderno di commonjs, ad esempio es2020:

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "../../dist/out-tsc",
    "module": "es2020",
    "types": ["node"],
  },
  "exclude": ["**/*.spec.ts", "**/*.test.ts"],
  "include": ["**/*.ts"]
}

Una volta fatto questo, il file compilato può essere eseguito utilizzando il solito nx serve.

Articoli correlati