Comment publier un module écrit en ES6 sur NPM?


144

J'étais sur le point de publier un module sur NPM, lorsque j'ai pensé à le réécrire dans ES6, à la fois pour le pérenniser et pour apprendre ES6. J'ai utilisé Babel pour transpiler vers ES5 et exécuter des tests. Mais je ne sais pas trop comment procéder:

  1. Dois-je transpiler et publier le dossier résultant / de sortie sur NPM?
  2. Dois-je inclure le dossier de résultats dans mon dépôt Github?
  3. Ou dois-je maintenir 2 dépôts, un avec le code ES6 + le script gulp pour Github, et un avec les résultats transpilés + les tests pour NPM?

En bref: quelles étapes dois-je suivre pour publier un module écrit en ES6 sur NPM, tout en permettant aux gens de parcourir / fourcher le code d'origine?


J'ai eu du mal avec cette décision ces derniers temps. Je vois que la réponse que vous avez qualifiée de correcte par José est également le consensus.
talves

Voici ma réponse 2018 , en tenant compte des progrès réalisés avec le support des modules depuis 2015.
Dan Dascalescu

1
J'adorerais si je pouvais faire le contraire. Utilisez un module ES pour importer un module NPM, mais ce sont les seuls résultats que j'obtiens.
SeanMC

Réponses:


81

Le modèle que j'ai vu jusqu'à présent est de conserver les fichiers es6 dans un srcrépertoire et de créer vos éléments dans la prépublication de npm dans le librépertoire.

Vous aurez besoin d'un fichier .npmignore, similaire à .gitignore mais ignorant srcau lieu de lib.


4
Avez-vous un référentiel d'exemples?
Ahmed Abbas

2
@JamesAkwuh Notez que vous aurez probablement envie de changer les commandes « start » et « construction » dans le package.json d'utiliser le chemin relatif du babel-cli: ./node_modules/babel-cli/bin/babel.js -s inline -d lib -w src. Cela devrait garantir que les installations n'échouent pas lors du déploiement dans de nouveaux environnements.
phazonNinja

2
@phazonNinja npm le gère
James Akwuh

4
«S'il n'y a pas de fichier .npmignore, mais qu'il existe un fichier .gitignore, alors npm ignorera les éléments correspondant au fichier .gitignore.» docs officiels npm
Frank Nocke

10
Au lieu de cela, .npmignorevous pouvez utiliser le fileschamp dans package.json . Il vous permet de spécifier exactement les fichiers que vous souhaitez publier, au lieu de rechercher des fichiers aléatoires que vous ne souhaitez pas publier.
Dan Dascalescu

76

J'aime la réponse de José. J'ai déjà remarqué que plusieurs modules suivent ce modèle. Voici comment vous pouvez facilement l'implémenter avec Babel6. J'installe babel-clilocalement pour que la construction ne casse pas si jamais je change ma version globale de babel.

.npmignore

/src/

.gitignore

/lib/
/node_modules/

Installez Babel

npm install --save-dev babel-core babel-cli babel-preset-es2015

package.json

{
  "main": "lib/index.js",
  "scripts": {
    "prepublish": "babel src --out-dir lib"
  },
  "babel": {
    "presets": ["es2015"]
  }
}

29
Toutes les commandes du scriptsseront node_modules/.binajoutées à leur $PATHet depuis babel-cliinstalle un binaire, node_modules/.bin/babelil n'est pas nécessaire de référencer la commande par chemin.
Sukima

3
Veuillez noter que cela prepublishpose problème car il pourrait s'exécuter au moment de l'installation ( github.com/npm/npm/issues/3059 ), préférez le versionhook de script plus idiomatique ( docs.npmjs.com/cli/version )
mattecapu

@mattecapu il semble que le problème prepublishsoit toujours là. Pour le moment, je pense compiler manuellement le srcrépertoire et npm publishc'est la voie à suivre.
sonlexqt

1
Vous pouvez utiliser le prepublishOnlyhook de script (voir docs.npmjs.com/misc/scripts#prepublish-and-prepare ). Notez que dans la version 5 de npm, cela devrait fonctionner comme prévu, mais pour le moment (en supposant que vous utilisez npm v4 +), cela devrait fonctionner.
Alex Mann

1
@FrankNocke prepublishs'exécute avant publish(obv.), Ce qui pousse les choses vers npm (ou partout où vous configurez). C'est donc pour créer ce qui se trouve dans le package NPM, même s'il n'est pas enregistré.
Simon Buchan

42

TL; DR - Ne le faites pas, jusqu'au ~ octobre 2019. L' équipe des modules Node.js a demandé :

Veuillez ne publier aucun package de module ES destiné à être utilisé par Node.js avant [octobre 2019]

Mise à jour de mai 2019

Depuis 2015, date à laquelle cette question a été posée, la prise en charge de JavaScript pour les modules a considérablement mûri et, espérons-le, sera officiellement stable en octobre 2019. Toutes les autres réponses sont désormais obsolètes ou trop compliquées. Voici la situation actuelle et les meilleures pratiques.

Prise en charge ES6

99% de ES6 (aka 2015) est pris en charge par Node depuis la version 6 . La version actuelle de Node est 12. Tous les navigateurs Evergreen prennent en charge la grande majorité des fonctionnalités ES6. ECMAScript est maintenant à la version 2019 , et le schéma de gestion des versions favorise désormais l'utilisation des années.

Modules ES (également appelés modules ECMAScript) dans les navigateurs

Tous les navigateurs Evergreen prennent en charge les import modules ES6 depuis 2017. Les importations dynamiques sont prises en charge par Chrome (+ forks comme Opera et Samsung Internet) et Safari. La prise en charge de Firefox est prévue pour la prochaine version, 67.

Vous n'avez plus besoin de Webpack / rollup / Parcel etc. pour charger des modules. Ils peuvent être encore utiles à d'autres fins, mais ne sont pas nécessaires pour charger votre code. Vous pouvez importer directement des URL pointant vers le code des modules ES.

Modules ES dans Node

Les modules ES ( .mjsfichiers avec import/ export) sont pris en charge depuis Node v8.5.0 en appelant nodeavec l' --experimental-modulesindicateur. Node v12, sorti en avril 2019, a réécrit le support des modules expérimentaux. Le changement le plus visible est que l'extension de fichier doit être spécifiée par défaut lors de l'importation:

// lib.mjs 

export const hello = 'Hello world!';

// index.mjs:

import { hello } from './lib.mjs';
console.log(hello);

Notez les .mjsextensions obligatoires partout. Courir comme:

node --experimental-modules index.mjs

La version Node 12 correspond également au moment où l'équipe des modules a demandé aux développeurs de ne pas publier de packages de modules ES destinés à être utilisés par Node.js jusqu'à ce qu'une solution soit trouvée pour utiliser les packages via require('pkg')et import 'pkg'. Vous pouvez toujours publier des modules ES natifs destinés aux navigateurs.

Prise en charge de l'écosystème des modules ES natifs

En mai 2019, le support de l'écosystème pour les modules ES est immature. Par exemple, les frameworks de test comme Jest et Ava ne prennent pas en charge --experimental-modules. Vous devez utiliser un transpilateur, et devez ensuite décider entre utiliser la import { symbol }syntaxe nommée import ( ) (qui ne fonctionnera pas encore avec la plupart des packages npm) et la syntaxe d'importation par défaut ( import Package from 'package'), qui fonctionne, mais pas lorsque Babel l'analyse pour les paquets créés en TypeScript (graphql-tools, node-influx, faast etc.) Il existe cependant une solution de contournement qui fonctionne à la fois avec --experimental-moduleset si Babel transpile votre code afin que vous puissiez le tester avec Jest / Ava / Mocha etc:

import * as ApolloServerM from 'apollo-server'; const ApolloServer = ApolloServerM.default || ApolloServerM;

Sans doute moche, mais de cette façon, vous pouvez écrire votre propre code de modules ES avec import/ exportet l'exécuter avec node --experimental-modules, sans transpilers. Si vous avez des dépendances qui ne sont pas encore prêtes pour ESM, importez-les comme ci-dessus, et vous pourrez utiliser des frameworks de test et d'autres outils via Babel.


Réponse précédente à la question - rappelez-vous, ne le faites pas tant que Node n'a pas résolu le problème de demande / importation, espérons-le vers octobre 2019.

Publication de modules ES6 sur npm, avec compatibilité descendante

Pour publier un module ES sur npmjs.org afin qu'il puisse être importé directement, sans Babel ou d'autres transpileurs, pointez simplement le mainchamp de votre package.jsonvers le .mjsfichier, mais omettez l'extension:

{
  "name": "mjs-example",
  "main": "index"
}

C'est le seul changement. En omettant l'extension, Node recherchera d'abord un fichier mjs s'il est exécuté avec --experimental-modules. Sinon, il reviendra au fichier .js, donc votre processus de transpilation existant pour prendre en charge les anciennes versions de Node fonctionnera comme avant - assurez-vous simplement de pointer Babel vers le ou les .mjsfichiers.

Voici la source d'un module ES natif avec rétrocompatibilité pour Node <8.5.0 que j'ai publié sur NPM. Vous pouvez l'utiliser dès maintenant, sans Babel ou autre chose.

Installez le module:

npm install local-iso-dt
# or, yarn add local-iso-dt

Créez un fichier test test.mjs :

import { localISOdt } from 'local-iso-dt/index.mjs';
console.log(localISOdt(), 'Starting job...');

Exécutez le nœud (v8.5.0 +) avec l'indicateur --experimental-modules:

node --experimental-modules test.mjs

Manuscrit

Si vous développez en TypeScript, vous pouvez générer du code ES6 et utiliser des modules ES6:

tsc index.js --target es6 --modules es2015

Ensuite, vous devez renommer la *.jssortie en .mjs, un problème connu qui, espérons-le, sera bientôt résolu afin de tscpouvoir générer .mjsdirectement les fichiers.


3
Dire "Tous les navigateurs Evergreen prennent en charge la grande majorité des fonctionnalités ES6." ne veut pas dire grand-chose quand on regarde les données et que l'on se rend compte que la prise en charge d'es6 dans les navigateurs n'atteint qu'environ 80% de tous les utilisateurs.
Pedro Pedrosa

3
Actuellement, l'écosystème n'est certainement pas assez mature pour cela. L'équipe Node.js avec la sortie de la v12 a spécifiquement demandé: "Veuillez ne pas publier de packages de modules ES destinés à être utilisés par Node.js tant que cela n'est pas résolu." 2ality.com/2019/04/nodejs-esm-impl.html#es-modules-on-npm Mocha ne prend pas en charge nativement les fichiers .mjs. Plusieurs bibliothèques (par exemple, create-react-app, react-apollo, graphql-js) avaient des problèmes avec les dépendances contenant des mjsfichiers. Node.js prévoit de déployer un support officiel en octobre 2019, ce qui est le premier que je revois sérieusement.
thisismydesign

3
@thisismydesign: merci pour le rappel de mettre à jour cette ancienne réponse! Je viens de le faire.
Dan Dascalescu

17

@Jose a raison. Il n'y a rien de mal à publier ES6 / ES2015 sur NPM, mais cela peut causer des problèmes, en particulier si la personne qui utilise votre package utilise Webpack, par exemple, car normalement les gens ignorent le node_modulesdossier lors du prétraitement avec babelpour des raisons de performances.

Donc, utilisez simplement gulp, gruntou simplement Node.js pour créer un libdossier qui est ES5.

Voici mon build-lib.jsscript, dans lequel je garde ./tools/(non gulpou gruntici):

var rimraf = require('rimraf-promise');
var colors = require('colors');
var exec = require('child-process-promise').exec;

console.log('building lib'.green);

rimraf('./lib')
    .then(function (error) {
        let babelCli = 'babel --optional es7.objectRestSpread ./src --out-dir ./lib';
        return exec(babelCli).fail(function (error) {
            console.log(colors.red(error))
        });
    }).then(() => console.log('lib built'.green));

Voici un dernier conseil: vous devez ajouter un .npmignore à votre projet . S'il npm publishne trouve pas ce fichier, il l'utilisera à la .gitignoreplace, ce qui vous posera des problèmes car normalement votre .gitignorefichier sera exclu ./libet inclus ./src, ce qui est exactement le contraire de ce que vous voulez lorsque vous publiez sur NPM. Le .npmignorefichier a fondamentalement la même syntaxe que .gitignore(AFAIK).


1
Au lieu de cela, .npmignorevous pouvez utiliser le fileschamp dans package.json . Il vous permet de spécifier exactement les fichiers que vous souhaitez publier, au lieu de rechercher des fichiers aléatoires que vous ne souhaitez pas publier.
Dan Dascalescu

Est-ce que cela ne fera pas trembler les arbres?
Joe le

Je ne recommande pas d' utiliser .npmignore, essayer package.json« s à la filesplace, voir: github.com/c-hive/guides/blob/master/js/...
thisismydesign

@thisismydesign: c'est exactement ce que j'avais recommandé dans mon commentaire ci-dessus ..?
Dan Dascalescu

Mon mauvais, je n'ai pas remarqué :)
thisismydesign

8

Suivant l'approche de José et Marius, (avec mise à jour de la dernière version de Babel en 2019): Conservez les derniers fichiers JavaScript dans un répertoire src, et construisez avec le prepublishscript de npm et sortez dans le répertoire lib.

.npmignore

/src

.gitignore

/lib
/node_modules

Installer Babel (version 7.5.5 dans mon cas)

$ npm install @babel/core @babel/cli @babel/preset-env --save-dev

package.json

{
  "name": "latest-js-to-npm",
  "version": "1.0.0",
  "description": "Keep the latest JavaScript files in a src directory and build with npm's prepublish script and output to the lib directory.",
  "main": "lib/index.js",
  "scripts": {
    "prepublish": "babel src -d lib"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.5.5",
    "@babel/core": "^7.5.5",
    "@babel/preset-env": "^7.5.5"
  },
  "babel": {
    "presets": [
      "@babel/preset-env"
    ]
  }
}

Et j'ai src/index.jsqui utilise la fonction de flèche:

"use strict";

let NewOneWithParameters = (a, b) => {
  console.log(a + b); // 30
};
NewOneWithParameters(10, 20);

Voici le dépôt sur GitHub .

Vous pouvez maintenant publier le package:

$ npm publish
...
> latest-js-to-npm@1.0.0 prepublish .
> babel src -d lib

Successfully compiled 1 file with Babel.
...

Avant que le package ne soit publié sur npm, vous verrez qu'il lib/index.jsa été généré, qui est transpilé en es5:

"use strict";

var NewOneWithParameters = function NewOneWithParameters(a, b) {
  console.log(a + b); // 30
};

NewOneWithParameters(10, 20);

[Update for Rollup bundler]

Comme demandé par @kyw, comment intégreriez-vous le bundler Rollup?

Tout d'abord, installez rollupetrollup-plugin-babel

npm install -D rollup rollup-plugin-babel

Deuxièmement, créez rollup.config.jsdans le répertoire racine du projet

import babel from "rollup-plugin-babel";

export default {
  input: "./src/index.js",
  output: {
    file: "./lib/index.js",
    format: "cjs",
    name: "bundle"
  },
  plugins: [
    babel({
      exclude: "node_modules/**"
    })
  ]
};

Enfin, mettez prepublishà jour danspackage.json

{
  ...
  "scripts": {
    "prepublish": "rollup -c"
  },
  ...
}

Maintenant, vous pouvez exécuter npm publish, et avant que le paquet ne soit publié sur npm, vous verrez que lib / index.js a été généré, qui est transpilé en es5:

'use strict';

var NewOneWithParameters = function NewOneWithParameters(a, b) {
  console.log(a + b); // 30
};

NewOneWithParameters(10, 20);

Remarque: en passant, vous n'en avez plus besoin @babel/clisi vous utilisez le bundler Rollup. Vous pouvez le désinstaller en toute sécurité:

npm uninstall @babel/cli

Comment intégreriez-vous le bundler Rollup?
kyw

1
@kyw, pour savoir comment intégrer le bundler Rollup, voir ma réponse mise à jour.
Yuci

Mise à jour de décembre 2019 -> github.com/rollup/rollup/blob
...

6

Si vous voulez voir cela en action dans un petit module Node open source très simple, jetez un œil à nth-day (que j'ai commencé - également d'autres contributeurs). Regardez dans le fichier package.json et à l'étape de pré-publication qui vous mènera à où et comment faire cela. Si vous clonez ce module, vous pouvez l'exécuter localement et l'utiliser comme modèle pour vous.


4

Node.js 13.2.0+ prend en charge ESM sans l'indicateur expérimental et il existe quelques options pour publier des packages NPM hybrides (ESM et CommonJS) (en fonction du niveau de compatibilité descendante nécessaire): https://2ality.com/2019 /10/hybrid-npm-packages.html

Je recommande d'utiliser la méthode de compatibilité descendante complète pour faciliter l'utilisation de votre package. Cela pourrait ressembler à ceci:

Le package hybride contient les fichiers suivants:

mypkg/
  package.json
  esm/
    entry.js
  commonjs/
    package.json
    entry.js

mypkg/package.json

{
  "type": "module",
  "main": "./commonjs/entry.js",
  "exports": {
    "./esm": "./esm/entry.js"
  },
  "module": "./esm/entry.js",
  ···
}

mypkg/commonjs/package.json

{
  "type": "commonjs"
}

Importation depuis CommonJS:

const {x} = require('mypkg');

Importation depuis ESM:

import {x} from 'mypkg/esm';

Nous avons fait une enquête sur le support ESM en 05.2019 et avons constaté que de nombreuses bibliothèques manquaient de support (d'où la recommandation de compatibilité descendante):


Je n'arrive pas à importer les modules ES6 installés globalement dans le dossier node_modules (fourni par npm root -g). Ne sommes-nous vraiment pas censés pouvoir faire cela? Je suis vraiment confus. Je sais que le lien npm peut résoudre le problème en liant le module à mon dossier node_modules local, mais je veux savoir pourquoi l'importation de modules de nœud global n'est pas prise en charge.
Joakim L. Christiansen

En me répondant, je suppose que cela ne sera jamais pris en charge: github.com/nodejs/node-eps/blob/master/ ... C'est une décision vraiment stupide, ce serait facile à soutenir ...
Joakim L. Christiansen

3

Les deux critères d'un package NPM sont qu'il est utilisable avec rien de plus qu'un require( 'package' )et fait quelque chose de logiciel-ish.

Si vous remplissez ces deux conditions, vous pouvez faire ce que vous voulez. Même si le module est écrit en ES6, si l'utilisateur final n'a pas besoin de le savoir, je le transpilerais pour l'instant pour obtenir un support maximal.

Cependant, si, comme koa , votre module nécessite une compatibilité avec les utilisateurs utilisant les fonctionnalités ES6, la solution à deux packages serait peut-être une meilleure idée.

À emporter

  1. Ne publiez que la quantité de code dont vous avez besoin pour require( 'your-package' )fonctionner.
  2. À moins que les ES5 et 6 ne comptent pour l'utilisateur, ne publiez qu'un seul package. Transpilez-le si vous le devez.

1
Cela ne semble pas répondre à la question. Je pense que l'OP essaie de comprendre comment structurer son dépôt Github et ce qu'il faut publier sur NPM et tout ce que vous avez dit, c'est qu'ils peuvent faire ce qu'ils veulent. Le PO souhaite des recommandations spécifiques sur une bonne pratique pour cette situation.
jfriend00

@ jfriend00 Je ne suis pas d'accord. J'ai recommandé qu'il transpile et ne publie que les fichiers nécessaires require( 'package' )au fonctionnement. Je vais modifier ma réponse pour que cela soit plus clair. Cela dit, la réponse de Jose est bien meilleure que la mienne.
JoshWillik

La réponse de José est très bonne, mais j'apprécie celle-ci pour avoir décrit explicitement de bonnes règles empiriques pour savoir quand / pourquoi utiliser un paquet contre deux.
Jordan Grey le

3

La clé principale package.jsondécide du point d'entrée du package une fois qu'il est publié. Ainsi, vous pouvez placer la sortie de votre Babel où vous voulez et avoir juste à mentionner le bon chemin dans la mainclé.

"main": "./lib/index.js",

Voici un article bien écrit sur la façon de publier un package npm

https://codeburst.io/publish-your-own-npm-package-ff918698d450

Voici un exemple de dépôt que vous pouvez utiliser comme référence

https://github.com/flexdinesh/npm-module-boilerplate


Cet article omet le fait que vous pouvez (et devriez) publier sur des modules NPM créés dans ES6 et importcapables directement sans avoir besoin de Babel ou de tout autre transpileur.
Dan Dascalescu

0

En fonction de l'anatomie de votre module, cette solution peut ne pas fonctionner, mais si votre module est contenu dans un seul fichier, et n'a pas de dépendances (n'utilise pas l' importation ), en utilisant le modèle suivant, vous pouvez publier votre code tel quel , et pourra être importé avec import (modules du navigateur ES6) et nécessiter (modules Node CommonJS)

En prime, il pourra être importé à l'aide d'un élément HTML SCRIPT.

main.js :

(function(){
    'use strict';
    const myModule = {
        helloWorld : function(){ console.log('Hello World!' )} 
    };

    // if running in NODE export module using NODEJS syntax
    if(typeof module !== 'undefined') module.exports = myModule ;
    // if running in Browser, set as a global variable.
    else window.myModule = myModule ;
})()

mon-module.js :

    // import main.js (it will declare your Object in the global scope)
    import './main.js';
    // get a copy of your module object reference
    let _myModule = window.myModule;
    // delete the the reference from the global object
    delete window.myModule;
    // export it!
    export {_myModule as myModule};

package.json : `

    {
        "name" : "my-module", // set module name
        "main": "main.js",  // set entry point
        /* ...other package.json stuff here */
    }

Pour utiliser votre module, vous pouvez désormais utiliser la syntaxe régulière ...

Lors de l'importation dans NODE ...

    let myModule = require('my-module');
    myModule.helloWorld();
    // outputs 'Hello World!'

Lors de l'importation dans le NAVIGATEUR ...

    import {myModule} from './my-module.js';
    myModule.helloWorld();
    // outputs 'Hello World!'

Ou même lorsqu'il est inclus à l'aide d'un élément de script HTML ...

<script src="./main.js"></script>
<script>
     myModule.helloWorld();
    // outputs 'Hello World!'
</script>

-1

Quelques notes supplémentaires pour tout le monde, en utilisant ses propres modules directement depuis github, sans passer par les modules publiés :

Le hook ( largement utilisé ) "Prepublish" ne fait rien pour vous.

La meilleure chose à faire (si vous prévoyez de s'appuyer sur des dépôts github, pas sur des éléments publiés):

  • annuler la liste srcde .npmignore (en d'autres termes: autorisez-le). Si vous n'en avez pas .npmignore, rappelez-vous: Une copie de .gitignoresera utilisée à la place dans l'emplacement d'installation, comme vous le ls node_modules/yourProjectverrez.
  • assure-toi, babel-cli que votre module est une dépendance, pas seulement un devDepenceny puisque vous construisez en effet sur la machine consommatrice, c'est-à-dire sur l'ordinateur des développeurs d'applications, qui utilise votre module
  • faire la construction, dans le crochet d'installation, c'est-à-dire:

    "install": "babel src -d lib -s"

(aucune valeur ajoutée en essayant quoi que ce soit de "préinstall", c'est-à-dire que babel-cli pourrait manquer)


3
Compiler lors de l'installation est très impoli. Ne faites pas cela - le temps d'installation de npm est déjà assez mauvais! Pour le code interne où vous souhaitez éviter d'utiliser un référentiel de packages npm, vous pouvez: 1) utiliser un monorepo, 2) télécharger et dépendre de la npm packsortie, 3) archiver la sortie de la construction.
Simon Buchan
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.