Cómo exportar múltiples módulos ES6 desde un paquete NPM

16

He creado un paquete NPM relativamente pequeño que consta de aproximadamente 5 clases ES6 diferentes contenidas en un archivo cada una, todas se parecen a esto:

export default class MyClass {
    // ...
}

Luego configuré un punto de entrada para mi paquete que se ve así:

export { default as MyClass } from './my-class.js';
export { default as MyOtherClass } from './my-other-class.js';

Luego ejecuté el punto de entrada a través de webpack y babel terminando con un index.js transpilado y minificado

Instalar e importar el paquete funciona bien, pero cuando hago lo siguiente desde mi código de cliente:

import { MyClass } from 'my-package';

No solo importa "MyClass", importa todo el archivo, incluidas todas las dependencias de cada clase (algunas de mis clases tienen enormes dependencias).

Supuse que así es como funciona webpack cuando intentas importar partes de un paquete ya incluido. Así que configuré mi configuración de paquete web local para ejecutar también a node_modules/my-packagetravés de babel y luego intenté:

import { MyClass } from 'my-package/src/index.js';

Pero incluso esto importa todas las clases exportadas por index.js. Lo único que parece funcionar de la manera que quiero es si lo hago:

import MyClass from 'my-package/src/my-class.js';

Pero prefiero mucho:

  1. Poder importar el archivo transpilado y minificado para que no tenga que decirle a webpack que ejecute babel dentro de node_modules y
  2. Poder importar cada clase individual directamente desde mi punto de entrada en lugar de tener que ingresar la ruta a cada archivo

¿Cuál es la mejor práctica aquí? ¿Cómo logran otros configuraciones similares? Me di cuenta de que GlideJS tiene una versión ESM de su paquete que le permite importar solo las cosas que necesita sin tener que ejecutar babel a través de él, por ejemplo.

El paquete en cuestión: https://github.com/powerbuoy/sleek-ui

webpack.config.js

const path = require('path');

module.exports = {
    entry: {
        'sleek-ui': './src/js/sleek-ui.js'
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist'),
        library: 'sleek-ui', // NOTE: Before adding this and libraryTarget I got errors saying "MyClass() is not a constructor" for some reason...
        libraryTarget: 'umd'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'babel-loader',
                        options: {
                            presets: ['@babel/preset-env']
                        }
                    }
                ]
            }
        ]
    }
};

package.json

  "name": "sleek-ui",
  "version": "1.0.0",
  "description": "Lightweight SASS and JS library for common UI elements",
  "main": "dist/sleek-ui.js",
  "sideEffects": false, // NOTE: Added this from Abhishek's article but it changed nothing for me :/
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/powerbuoy/sleek-ui.git"
  },
  "author": "Andreas Lagerkvist",
  "license": "GPL-2.0-or-later",
  "bugs": {
    "url": "https://github.com/powerbuoy/sleek-ui/issues"
  },
  "homepage": "https://github.com/powerbuoy/sleek-ui#readme",
  "devDependencies": {
    "@babel/core": "^7.8.6",
    "@babel/preset-env": "^7.8.6",
    "babel-loader": "^8.0.6",
    "webpack": "^4.42.0",
    "webpack-cli": "^3.3.11"
  },
  "dependencies": {
    "@glidejs/glide": "^3.4.1",
    "normalize.css": "^8.0.1"
  }
}
powerbuoy
fuente
1
¿Agregó el mainatributo (punto de entrada) en el paquete package.json de su lib? Revisa tu construcción. ¿Y cómo está empaquetando su paquete lib?
Abhishek
La propiedad principal de un package.json es una dirección al punto de entrada al módulo que describe el package.json. En una aplicación Node.js, cuando se llama al módulo a través de una instrucción require, las exportaciones del módulo desde el archivo nombrado en la propiedad principal serán lo que se devuelve a la aplicación Node.js.
Abhishek
Sí, la propiedad principal apunta a mi index.js que exporta todas las demás clases. Estoy empaquetando el archivo main / index.js usando webpack y babel. Todo se explica en la pregunta.
Powerbuoy
esto podría ayudarlo - danielberndt.net/blog/2018/…
Abhishek
También puede en su implementación de compilación: github.com/mui-org/material-ui/blob/master/packages/material-ui / ... Para tener un tamaño de compilación más corto, es mejor hacerlo import { MyClass } from 'my-package/src/MyClass';. También puede eliminar el paquete de compilación src para acortar la ruta del archivo.
Abhishek

Respuestas:

1

Supuse que así es como funciona webpack cuando intentas importar partes de un paquete ya incluido.

Sí, la forma en que lo configuró es importando cada clase en index.js, que luego se transpira en un archivo (si está dirigido a ES5, que es lo más común *). Esto significa que cuando ese archivo se importa en otro archivo, viene en su totalidad, con todas esas clases.

Si desea una correcta sacudida del árbol, debe evitar transpilarlo en un paquete CommonJS (ES5). Mi sugerencia es mantener los módulos ES6, ya sea solos o en una ubicación separada del paquete ES5. Este artículo debería ayudarlo a comprender completamente esto y tiene instrucciones recomendadas. Básicamente, se reduce a configurar el entorno de Babel usando preestablecido-env (¡muy recomendable si aún no lo está usando!) Para preservar la sintaxis de ES6 . Aquí está la configuración de Babel relevante, si no desea transpilar a ES5:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "esmodules": true
        }
      }
    ]
  ]
}

El artículo detalla cómo configurar 2 paquetes, cada uno con una sintaxis de módulo diferente.

También vale la pena señalar, y también se menciona en el artículo, puede establecer el punto de entrada del módulo ES en package.json. Eso le dice a Webpack / Babel dónde se pueden encontrar los módulos ES6, que pueden ser todo lo que necesita para su caso de uso. Parece que la sabiduría convencional dice hacer:

{
  "main": "dist/sleek-ui.js",
  "module": "src/main.js"
}

Pero la documentación del nodo lo tiene como:

{
  "type": "module",
  "main": "dist/sleek-ui.js",
  "exports": {
    ".": "dist/sleek-ui.js",
    "./module": "src/main.js"
  }
}

Si tuviera tiempo, jugaría con esto y vería cuál funciona correctamente, pero esto debería ser suficiente para ubicarlo en el camino correcto.


* Los paquetes dirigidos a ES5 están en formato CommonJS, que tiene que incluir todos los archivos asociados, porque ES5 no tiene soporte para módulos nativos. Eso vino en ES2015 / ES6.

CaitlinWeb
fuente
Intenté agregar targets.esmodules: truey aunque eso hizo cambios en el script creado, no hizo ningún cambio en cuanto a lo que se importó al final. Importar una sola clase de my-packagetodavía importa todo. También probé los cambios en package.json(junto con el otro cambio) y eso tampoco cambió nada. Bueno, agregar type: modulerealmente rompió mi compilación con "Debe usar la importación para cargar el módulo ES: /sleek-ui/webpack.config.js require () de los módulos ES no es compatible". así que tuve que quitar ese bit Echaré un vistazo al artículo vinculado.
Powerbuoy
Ok, el artículo realmente me dijo que lo configurara modules: false(no dentro targets) pero tampoco funcionó ... Creo que solo importaré directamente desde el archivo fuente y seguiré ejecutando babel a través de node_modules hasta que podamos usar esto de forma nativa.
Powerbuoy
@powerbuoy Importar desde el archivo fuente funciona. Tal vez no estaba claro en mi publicación, y si ese es el caso, puedo editarlo, pero desea importar solo la clase como import MyClass from 'my-package/myClass';. Un buen ejemplo de repositorio de esto es lodash-es .
CaitlinWeb
-1

Este es un caso de uso válido. El objetivo final es hacer esto, import { MyClass } from 'my-package'pero hay una forma más limpia de hacerlo.

Cree un archivo de índice agregador en su my-package. Básicamente my-package/index.jsy debería verse así:

import MyClass from './my-class.js'
import MyOtherClass from './my-other-class.js'

export { MyClass, MyOtherClass }

Entonces puedes hacer import { MyClass } from 'my-package'. Pan comido.

¡Que te diviertas!

idancali
fuente
Esto es exactamente lo que estoy haciendo ya, lo que pensé que estaba bastante claro en la pregunta.
Powerbuoy