Búsqueda de sitios web

node.js - Por qué TypeScript importa CommonJS en lugar de ESM


Seguí este tutorial para crear un paquete dual. Así es como se ve la estructura de archivos del paquete:

my-awesome-lib
  package.json
  dist
  |-- mjs
    |-- package.json
    |-- index.js
  |-- cjs
    |-- package.json
    |-- index.js

En package.json:

  "main": "dist/cjs/index.js",
  "module": "dist/mjs/index.js",
  "exports": {
    ".": {
      "require": "./dist/cjs/index.js",
      "import": "./dist/mjs/index.js"
    }
  },

El package.json en la carpeta mjs:

{
    "type": "module"
}

El package.json en la carpeta cjs:

{
    "type": "commonjs"
}

En mi aplicación de Typescript, instalé mi propio paquete y traté de importarlo. Sin embargo, observé algo extraño:

// src/index.ts
import { Class } from 'my-awesome-lib'
import { Class } from '../node_modules/my-awesome-lib/dist/mjs/index.js';

La primera declaración de importación apunta a ../node_modules/my-awesome-lib/dist/cjs/index.js en lugar de la carpeta mjs. La segunda declaración de importación es la que realmente quiero.

¿Alguien puede decirme qué salió mal? ¿Es porque publiqué mi biblioteca de manera incorrecta?


p.d.

Este es mi tsconfig.json

{
    "compilerOptions": {
      "target": "es2020",
      "module": "es2020",
      "strict": true,
      "esModuleInterop": true,
      "skipLibCheck": true,
      "forceConsistentCasingInFileNames": true,
      "declaration": true,
      "moduleResolution": "node",
      "outDir": "dist"
    },
    "include": ["src"]
}  

p.d. agregar {"type": "module"} al package.json de la aplicación tampoco hace que este error desaparezca.

Respuesta #1

Todo está funcionando como fue diseñado. El problema que estás experimentando es un problema con lo que esperas. Tu archivo "tsconfig.json" está configurado incorrectamente para un módulo ESM, sin embargo, esperas que cargue el paquete ESM.

Parece que estás usando node16-strictest-esm-combined como referencia, lo cual, si es así, puedo decirte que esta configuración también me desconcertó hace aproximadamente un año.

Realmente me gusta el repositorio base/tsconfig, pero solo para configuraciones CJS. No sé por qué configuran un tsconfig que tiene ESM en su nombre usando "moduleResolution": "node", pero usar "node" como valor le dice a TypeScript que genere JavaScript que se resuelva como un módulo CommonJS.

Aquí está el enlace a la documentación oficial de moduleResolution

Muestra que la configuración siguiente (que es la configuración que sé al 100% que se transpila a ESM) es la configuración correcta para usar.

Esto es directamente de uno de mis proyectos que es un paquete "ESM".

{
    "compilerOptions": {
      "lib": ["ESNext"], // Why not use NodeNext if it compiles down anyways
      "target": "ES2021",

      // Runtime/Module
      "module": "Node16", // <- ESM Module Type
      "moduleResolution": "Node16", // <- ESM Resolution
      "esModuleInterop": false, // I added this to show what not to do

      // Project Structure
      "rootDir": "src",
      "outDir": "build",
      "declarationDir": "types",

      "strict": true,
      "alwaysStrict": false, // <- you can set this to false if your writing ESM
      "skipLibCheck": true,
      "forceConsistentCasingInFileNames": true
    },

    "include": ["src/**/*.ts", "src/**/*.mts"], // You might want to change this
    "exclude": ["build", "types", "node_modules"]
}  

Tienes 2 opciones

NÚMERO 1: Para asegurarte de que tu proyecto sea un ES-Módulo: vas a necesitar retranspilar tu proyecto. Tienes dos opciones. Puedes hacerlo de la manera que prefiero, que es la forma mostrada a continuación: usando las banderas de limpiar y forzar la compilación. Esas banderas adicionales aseguran que todo lo innecesario sea eliminado, y que todo lo necesario se retranspile incluso si no fue modificado. Esto es importante, porque estás cambiando el tipo de módulo.

En "ESM" tus archivos .js deberían emitirse con las importaciones realizadas usando la palabra clave import, en lugar de los métodos "CJS" require(). Debido a que tsc intenta ser eficiente, no recompilará los archivos que tengan require, porque los documentos .ts correspondientes no han sido modificados.

   $ tsc -b --clean
   $ tsc -b --force

NÚMERO 2: La otra ruta que puedes tomar es simplemente eliminar tus directorios build y type, y luego ejecutar $ tsc. Esta forma también funciona, pero requiere ya sea comandos adicionales en la terminal o hacer clic con el ratón, mientras que la otra forma son dos comandos tsc. Además, el primer método puede ser agregado a la propiedad scripts de tu package.json, y más tarde puede ser llamado nuevamente rápidamente con npm run. También puedes crear tareas de TypeScript para las banderas --build, --force y --clean.

De todos modos...

Asegúrate de que, una vez que hayas retranspilado todo tu proyecto (incluyendo todos los archivos), tus archivos de JavaScript estén usando las palabras clave import y export, y no la propiedad y el método requires("...") y module.exports. Solo entonces sabrás que realmente tienes un módulo ESM.

En este punto, la rama ESM de tu paquete debería cargarse

Si no es así, entonces sabes que es algo con la configuración de tus paquetes, en cuyo caso puedes enviarme un mensaje y puedo ver si puedo ayudar más.

Artículos relacionados