Comment exporter plusieurs modules ES6 à partir d'un package NPM

16

J'ai construit un package NPM relativement petit composé d'environ 5 classes ES6 différentes contenues dans un fichier chacune, elles ressemblent toutes à peu près à ceci:

export default class MyClass {
    // ...
}

J'ai ensuite configuré un point d'entrée pour mon package qui ressemble à ceci:

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

J'ai ensuite exécuté le point d'entrée via webpack et babel pour finir avec un index.js transpilé et minifié

L'installation et l'importation du package fonctionnent correctement, mais lorsque je fais ce qui suit à partir de mon code client:

import { MyClass } from 'my-package';

Il ne se contente pas d'importer "MyClass", il importe le fichier entier, y compris toutes les dépendances de chaque classe (certaines de mes classes ont d'énormes dépendances).

Je me suis dit que c'est ainsi que fonctionne le webpack lorsque vous essayez d'importer des parties d'un package déjà fourni? J'ai donc configuré ma configuration de webpack local pour exécuter Babel node_modules/my-packageaussi, puis j'ai essayé:

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

Mais même cela importe chaque classe exportée par index.js. La seule chose qui semble fonctionner comme je le souhaite, c'est si je le fais:

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

Mais je préfère de beaucoup:

  1. Pouvoir importer le fichier transpilé et minifié pour que je n'ai pas à dire à webpack d'exécuter babel dans node_modules et
  2. Être capable d'importer chaque classe individuelle directement à partir de mon point d'entrée au lieu d'avoir à entrer le chemin d'accès à chaque fichier

Quelle est la meilleure pratique ici? Comment les autres réalisent-ils des configurations similaires? J'ai remarqué que GlideJS a une version ESM de son package qui vous permet d'importer uniquement les choses dont vous avez besoin sans avoir à exécuter babel par exemple.

Le package en question: 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"
  }
}

bouée
la source
1
Avez-vous ajouté l' mainattribut (point d'entrée) dans le package.json de votre bibliothèque? Enregistrez votre build. Et comment vous regroupez votre paquet lib?
Abhishek
La propriété principale d'un package.json est une direction vers le point d'entrée du module que package.json décrit. Dans une application Node.js, lorsque le module est appelé via une instruction require, les exportations du module à partir du fichier nommé dans la propriété principale seront ce qui est retourné à l'application Node.js.
Abhishek
Oui, la propriété principale pointe vers mon index.js qui exporte toutes les autres classes. Je regroupe le fichier principal / index.js en utilisant webpack et babel. Tout est expliqué dans la question.
powerbuoy
cela pourrait vous aider - danielberndt.net/blog/2018/…
Abhishek
Vous pouvez également à leur implémentation de construction - github.com/mui-org/material-ui/blob/master/packages/material-ui/… Afin d'avoir une taille de construction plus courte, il est préférable de le faire import { MyClass } from 'my-package/src/MyClass';. Vous pouvez également supprimer le package de génération src pour raccourcir le chemin d'accès au fichier.
Abhishek

Réponses:

1

J'ai pensé que c'est ainsi que le webpack fonctionne lorsque vous essayez d'importer des parties d'un package déjà fourni?

Oui, la façon dont vous l'avez configurée consiste à importer chaque classe dans index.js, qui est ensuite transposé en un seul fichier (s'il cible ES5, ce qui est le plus courant *). Cela signifie que lorsque ce fichier est importé dans un autre fichier, il est livré dans son intégralité, avec toutes ces classes.

Si vous voulez un tremblement d'arbre approprié, vous devez éviter de le transpiler en un paquet CommonJS (ES5). Ma suggestion est de conserver les modules ES6, soit seuls, soit dans un emplacement séparé du bundle ES5. Cet article devrait vous aider à bien comprendre cela et a recommandé des instructions. Essentiellement, cela revient à définir l'environnement Babel à l'aide de preset-env (fortement recommandé si vous ne l'utilisez pas déjà!) Pour préserver la syntaxe ES6 . Voici la configuration Babel appropriée, si vous ne voulez pas transpiler vers ES5:

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

L'article explique comment configurer 2 bundles, chacun utilisant une syntaxe de module différente.

Il convient également de noter, et est également mentionné dans l'article, que vous pouvez définir le point d'entrée du module ES dans package.json. Cela indique à Webpack / Babel où se trouvent les modules ES6, ce qui peut être tout ce dont vous avez besoin pour votre cas d'utilisation. Il semble que la sagesse conventionnelle dit de faire:

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

Mais la documentation de Node l'a comme:

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

Si j'avais le temps, je jouerais avec cela et verrais ce qui fonctionne correctement, mais cela devrait être suffisant pour vous mettre sur la bonne voie.


* Les bundles ciblés par ES5 sont au format CommonJS, qui doit inclure tous les fichiers associés, car ES5 ne prend pas en charge les modules natifs. Cela est arrivé dans ES2015 / ES6.

CaitlinWeb
la source
J'ai essayé d'ajouter targets.esmodules: trueet bien que cela ait apporté des modifications au script construit, il n'a fait aucun changement quant à ce qui a été importé à la fin. L'importation d'une seule classe depuis my-packageimporte toujours tout. J'ai également essayé les changements dans package.json(avec l'autre changement) et cela n'a rien changé non plus. Eh bien, l'ajout a en type: modulefait cassé ma version avec "Doit utiliser l'importation pour charger le module ES: /sleek-ui/webpack.config.js require () des modules ES n'est pas pris en charge." j'ai donc dû retirer ce bit. Je vais jeter un œil à l'article lié.
powerbuoy
Ok donc l'article m'a en fait dit de définir modules: false(pas à l'intérieur targets) mais cela n'a pas fonctionné non plus ... Je pense que je vais juste importer directement à partir du fichier source et continuer à exécuter babel via node_modules jusqu'à ce que nous puissions utiliser ce genre de choses nativement.
powerbuoy
@powerbuoy L'importation à partir du fichier source fonctionne. Peut-être que ce n'était pas clair de mon message, et si c'est le cas, je peux le modifier, mais vous voulez importer uniquement la classe comme import MyClass from 'my-package/myClass';. Les lodash-es en sont un bon exemple .
CaitlinWeb
-1

Il s'agit d'un cas d'utilisation valide. Le but ultime étant de le faire, import { MyClass } from 'my-package'mais il existe une façon plus propre de le faire.

Créez un fichier d'index agrégateur dans votre fichier my-package. Fondamentalement my-package/index.js, cela devrait ressembler à ceci:

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

export { MyClass, MyOtherClass }

Ensuite, vous pouvez le faire import { MyClass } from 'my-package'. Peasy facile.

S'amuser!

idancali
la source
C'est exactement ce que je fais déjà, ce que je pensais être assez clair dans la question.
powerbuoy