Come pubblicare un modulo scritto in ES6 su NPM?


144

Stavo per pubblicare un modulo su NPM, quando ho pensato di riscriverlo in ES6, sia per renderlo a prova di futuro, sia per imparare ES6. Ho usato Babel per traspilare su ES5 ed eseguire test. Ma non sono sicuro di come procedere:

  1. Devo compilare e pubblicare la cartella risultante / out su NPM?
  2. Includo la cartella dei risultati nel mio repository Github?
  3. O mantengo 2 repository, uno con il codice ES6 + script gulp per Github e uno con i risultati + test traspilati per NPM?

In breve: quali passi devo prendere per pubblicare un modulo scritto in ES6 su NPM, pur consentendo alle persone di sfogliare / fork il codice originale?


Ho avuto difficoltà con questa decisione ultimamente. Vedo che la risposta contrassegnata come corretta da José è anche il consenso.
parla del

Ecco la mia risposta del 2018 , tenendo conto dei progressi con il supporto del modulo dal 2015.
Dan Dascalescu,

1
Mi piacerebbe se potessi fare il contrario. Utilizzare un modulo ES per importare un modulo NPM, ma questi sono gli unici risultati che ottengo.
SeanMC

Risposte:


81

Lo schema che ho visto finora è quello di mantenere i file es6 in una srcdirectory e creare le tue cose nella pre-pubblicazione di npm nella libdirectory.

Sarà necessario un file .npmignore, simile a .gitignore ma ignorato srcinvece di lib.


4
Hai un repository di esempio?
Ahmed Abbas,

2
@JamesAkwuh Si noti che è probabile che vuole cambiare "Start" e comandi "costruire" in package.json di utilizzare il percorso relativo del babele-cli: ./node_modules/babel-cli/bin/babel.js -s inline -d lib -w src. Ciò dovrebbe garantire che l'installazione non fallisca durante la distribuzione in nuovi ambienti.
phazonNinja,

2
@phazonNinja npm lo gestisce
James Akwuh,

4
"Se non esiste un file .npmignore, ma esiste un file .gitignore, allora npm ignorerà le cose associate al file .gitignore." documenti ufficiali npm
Frank Nocke

10
Invece di .npmignorete puoi usare il filescampo in package.json . Ti consente di specificare esattamente i file che vuoi pubblicare, invece di cercare file casuali che non vuoi pubblicare.
Dan Dascalescu,

76

Mi piace la risposta di José. Ho notato che diversi moduli seguono già questo schema. Ecco come puoi implementarlo facilmente con Babel6. Installo babel-clilocalmente in modo che la build non si interrompa se cambio mai la mia versione globale di babel.

.npmignore

/src/

.gitignore

/lib/
/node_modules/

Installa 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
Tutti i comandi in scriptssaranno node_modules/.binaggiunti al loro $PATHe poiché babel-cliinstalla un binario node_modules/.bin/babelnon è necessario fare riferimento al comando per percorso.
Sukima,

3
Si noti che prepublishè problematico perché potrebbe essere eseguito al momento dell'installazione ( github.com/npm/npm/issues/3059 ), preferire l' versionhook dello script più idiomatico ( docs.npmjs.com/cli/version )
mattecapu

@mattecapu sembra che il problema prepublishsia ancora lì. Al momento penso che compili manualmente la srcdirectory ed npm publishè la strada da percorrere.
Sonlexqt,

1
È possibile utilizzare l' prepublishOnlyhook degli script (consultare docs.npmjs.com/misc/scripts#prepublish-and-prepare ). Nota che nella versione 5 di npm dovrebbe funzionare come previsto, ma per ora (supponendo che tu stia usando npm v4 +) dovrebbe funzionare.
Alex Mann,

1
@FrankNocke prepublishviene eseguito prima publish(obv.), Che invia roba su npm (o ovunque tu configuri). Quindi serve per creare ciò che contiene il pacchetto NPM, anche se non è registrato.
Simon Buchan,

42

TL; DR - No, fino a ~ ottobre 2019. Il team di moduli Node.js ha chiesto :

Non pubblicare pacchetti di moduli ES destinati all'uso da Node.js fino a [ottobre 2019]

Aggiornamento di maggio 2019

Dal 2015, quando è stata posta questa domanda, il supporto JavaScript per i moduli è maturato in modo significativo e si spera sarà ufficialmente stabile nell'ottobre 2019. Tutte le altre risposte sono ora obsolete o eccessivamente complicate. Ecco la situazione attuale e le migliori pratiche.

Supporto ES6

Il 99% di ES6 (aka 2015) è stato supportato da Node dalla versione 6 . La versione corrente di Node è 12. Tutti i browser sempreverdi supportano la stragrande maggioranza delle funzionalità ES6. ECMAScript è ora alla versione 2019 e lo schema di versioning ora favorisce l'utilizzo degli anni.

Moduli ES (noti anche come moduli ECMAScript) nei browser

Tutti i browser sempreverdi sono state sostenendo import -Ing moduli ES6 dal 2017. importazioni dinamici sono supportati da Chrome (+ forchette come Opera e Samsung Internet) e Safari. Il supporto di Firefox è previsto per la prossima versione, 67.

Non è più necessario Webpack / rollup / Parcel ecc. Per caricare i moduli. Possono essere ancora utili per altri scopi, ma non sono tenuti a caricare il codice. È possibile importare direttamente gli URL che puntano al codice dei moduli ES.

Moduli ES nel nodo

I moduli ES ( .mjsfile con import/ export) sono supportati dal Nodo v8.5.0 chiamando nodecon il --experimental-modulesflag. Il nodo v12, rilasciato nell'aprile 2019, ha riscritto il supporto dei moduli sperimentali. La modifica più visibile è che l'estensione del file deve essere specificata per impostazione predefinita durante l'importazione:

// lib.mjs 

export const hello = 'Hello world!';

// index.mjs:

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

Nota le .mjsestensioni obbligatorie in tutto. Correre come:

node --experimental-modules index.mjs

La versione Nodo 12 è anche quando il team dei moduli ha chiesto agli sviluppatori di non pubblicare pacchetti di moduli ES destinati all'uso da Node.js fino a quando non viene trovata una soluzione per l'utilizzo dei pacchetti tramite entrambi require('pkg')e import 'pkg'. Puoi comunque pubblicare moduli ES nativi destinati ai browser.

Supporto ecosistemico di moduli ES nativi

A partire da maggio 2019, il supporto dell'ecosistema per i moduli ES è immaturo. Ad esempio, i framework di test come Jest e Ava non supportano --experimental-modules. È necessario utilizzare un transpiler e quindi decidere se utilizzare la import { symbol }sintassi denominata import ( ) (che non funzionerà ancora con la maggior parte dei pacchetti npm) e la sintassi di importazione predefinita ( import Package from 'package'), che funziona, ma non quando Babel lo analizza per i pacchetti creati in TypeScript (graphql-tools, node-influx, faast ecc.) Esiste comunque una soluzione alternativa che funziona sia con --experimental-modulesche se Babel traspila il codice in modo da poterlo testare con Jest / Ava / Mocha ecc:

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

Probabilmente brutto, ma in questo modo è possibile scrivere il proprio codice di moduli ES con import/ exported eseguirlo con node --experimental-modules, senza transpilers. Se hai dipendenze che non sono ancora pronte per ESM, importale come sopra e sarai in grado di utilizzare i framework di test e altri strumenti tramite Babel.


Risposta precedente alla domanda: ricorda, non farlo fino a quando Nodo non risolve il problema di richiesta / importazione, si spera intorno a ottobre 2019.

Pubblicazione di moduli ES6 su npm, con compatibilità con le versioni precedenti

Per pubblicare un modulo ES per npmjs.org in modo che possa essere importato direttamente, senza Babel o altro transpilers, è sufficiente puntare il maincampo nel package.jsonal .mjsfile, ma omettere l'estensione:

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

Questo è l'unico cambiamento. Omettendo l'estensione, Node cercherà prima un file mjs se eseguito con --experimental-modules. Altrimenti tornerà al file .js, quindi il tuo processo di transpilation esistente per supportare le versioni precedenti di Node funzionerà come prima - assicurati di puntare Babel ai .mjsfile.

Ecco la fonte di un modulo ES nativo con retrocompatibilità per Nodo <8.5.0 che ho pubblicato su NPM. Puoi usarlo adesso, senza Babele o altro.

Installa il modulo:

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

Creare un file di test test.mjs :

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

Esegui nodo (v8.5.0 +) con il flag --experimental-modules:

node --experimental-modules test.mjs

Dattiloscritto

Se sviluppi in TypeScript, puoi generare il codice ES6 e utilizzare i moduli ES6:

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

Quindi, è necessario rinominare l' *.jsoutput in .mjs, un problema noto che si spera venga risolto presto in modo da tscpoter emettere .mjsdirettamente i file.


3
Dicendo "Tutti i browser sempreverdi supportano la stragrande maggioranza delle funzionalità ES6". non significa molto quando si guardano i dati e ci si rende conto che il supporto es6 nei browser raggiunge solo l'80% circa di tutti gli utenti.
Pedro Pedrosa,

3
Attualmente, l'ecosistema non è assolutamente abbastanza maturo per questo. Il team di Node.js con il rilascio di v12 ha chiesto specificamente: "Non pubblicare pacchetti di moduli ES destinati all'uso da Node.js fino a quando questo non viene risolto." 2ality.com/2019/04/nodejs-esm-impl.html#es-modules-on-npm Mocha non supporta nativamente i file .mjs. Molte librerie (ad es. Create -eagire-app, reagire-apollo, graphql-js) presentavano problemi con dipendenze contenenti mjsfile. Node.js prevede di distribuire il supporto ufficiale nell'ottobre 2019, che è il primo che vorrei rivisitare seriamente.
thisismydesign

3
@thisismydesign: grazie per il promemoria per aggiornare questa vecchia risposta! L'ho appena fatto.
Dan Dascalescu,

17

@Jose ha ragione. Non c'è nulla di sbagliato nel pubblicare ES6 / ES2015 su NPM, ma ciò può causare problemi, specialmente se la persona che utilizza il pacchetto utilizza Webpack, ad esempio, perché normalmente le persone ignorano la node_modulescartella durante la preelaborazione babelper motivi di prestazioni.

Quindi, basta usare gulp, grunto semplicemente Node.js per costruire una libcartella che si trova ES5.

Ecco la mia build-lib.jssceneggiatura, in cui tengo ./tools/(no gulpo gruntqui):

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));

Ecco un ultimo consiglio: devi aggiungere un .npmignore al tuo progetto . Se npm publishnon trova questo file, verrà utilizzato .gitignoreinvece, il che causerà problemi perché normalmente il .gitignorefile verrà escluso./lib e incluso ./src, che è esattamente l'opposto di quello che si desidera quando si pubblica su NPM. Il .npmignorefile ha sostanzialmente la stessa sintassi di .gitignore(AFAIK).


1
Invece di .npmignorete puoi usare il filescampo in package.json . Ti consente di specificare esattamente i file che vuoi pubblicare, invece di cercare file casuali che non vuoi pubblicare.
Dan Dascalescu,

Questa pausa non scuote l'albero?
Joe,

Non consiglio usando .npmignore, prova a package.json's files, invece, vedi: github.com/c-hive/guides/blob/master/js/...
thisismydesign

@thisismydesign: è esattamente quello che avevo raccomandato nel mio commento sopra ..?
Dan Dascalescu,

Mio male, non me ne sono accorto :)
thisismydesign

8

Seguendo l'approccio di José e Marius, (con l'aggiornamento dell'ultima versione di Babel nel 2019): mantieni gli ultimi file JavaScript in una directory src, e costruisci con lo prepublishscript di npm ed esegui l' output nella directory lib.

.npmignore

/src

.gitignore

/lib
/node_modules

Installa Babel (versione 7.5.5 nel mio caso)

$ 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"
    ]
  }
}

E ho src/index.jsche utilizza la funzione freccia:

"use strict";

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

Ecco il repository su GitHub .

Ora puoi pubblicare il pacchetto:

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

Successfully compiled 1 file with Babel.
...

Prima che il pacchetto sia pubblicato su npm, vedrai che lib/index.jsè stato generato, che viene traspilato in es5:

"use strict";

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

NewOneWithParameters(10, 20);

[Aggiornamento per il bundler di rollup]

Come chiesto da @kyw, come integreresti il ​​bundle Rollup?

Innanzitutto, installa rolluperollup-plugin-babel

npm install -D rollup rollup-plugin-babel

In secondo luogo, creare rollup.config.jsnella directory principale del progetto

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/**"
    })
  ]
};

Infine, aggiornare prepublishinpackage.json

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

Ora puoi eseguire npm publish, e prima che il pacchetto sia pubblicato su npm, vedrai che è stato generato lib / index.js, che viene traspilato in es5:

'use strict';

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

NewOneWithParameters(10, 20);

Nota: a proposito, non è più necessario @babel/clise si utilizza il bundle Rollup. Puoi disinstallarlo in sicurezza:

npm uninstall @babel/cli

Come integreresti il ​​bundle Rollup?
kyw,

1
@kyw, riguardo a come integrare il bundle Rollup, vedi la mia risposta aggiornata.
Yuci,

Aggiornamento di dicembre 2019 -> github.com/rollup/rollup/blob/…
a.barbieri

6

Se vuoi vederlo in azione in un piccolo modulo Node open source molto semplice, dai un'occhiata all'ennesimo giorno (che ho iniziato - anche altri collaboratori). Guarda il file package.json e il passaggio di pre-pubblicazione che ti porterà dove e come farlo. Se cloni quel modulo puoi eseguirlo localmente e usarlo come modello per te.


4

Node.js 13.2.0+ supporta ESM senza flag sperimentale e ci sono alcune opzioni per pubblicare pacchetti NPM ibridi (ESM e CommonJS) (a seconda del livello di compatibilità con le versioni precedenti necessarie): https://2ality.com/2019 /10/hybrid-npm-packages.html

Ti consiglio di seguire la piena compatibilità con le versioni precedenti per semplificare l'utilizzo del tuo pacchetto. Questo potrebbe apparire come segue:

Il pacchetto ibrido ha i seguenti file:

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"
}

Importazione da CommonJS:

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

Importazione da ESM:

import {x} from 'mypkg/esm';

Abbiamo fatto un'indagine sul supporto ESM nel 05.2019 e abbiamo scoperto che molte librerie mancavano di supporto (da qui la raccomandazione per la compatibilità con le versioni precedenti):


Non riesco a importare moduli ES6 installati a livello globale nella cartella node_modules (fornita da npm root -g). Non dovremmo davvero essere in grado di farlo? Sono davvero confuso. So che npm link può risolvere il problema collegando il modulo alla mia cartella node_modules locale, ma voglio sapere perché l'importazione dei moduli di nodo globale non è supportata.
Joakim L. Christiansen,

Rispondendo a me stesso, suppongo che non sarà mai supportato: github.com/nodejs/node-eps/blob/master/… È una decisione davvero stupida, sarebbe facile da sostenere ...
Joakim L. Christiansen

3

I due criteri di un pacchetto NPM è che è utilizzabile con nient'altro che a require( 'package' ) e fa qualcosa di software-ish.

Se soddisfi questi due requisiti, puoi fare quello che desideri. Anche se il modulo è scritto in ES6, se l'utente finale non ha bisogno di saperlo, per ora lo traspilerei per ottenere il massimo supporto.

Tuttavia, se piace koa , il tuo modulo richiede compatibilità con gli utenti che utilizzano le funzionalità ES6, allora forse la soluzione a due pacchetti sarebbe un'idea migliore.

Porta via

  1. Pubblica solo tutto il codice necessario per far require( 'your-package' )funzionare.
  2. A meno che tra ES5 e 6 non contenga l'utente, pubblicare solo 1 pacchetto. Transpile se è necessario.

1
Questo non sembra rispondere alla domanda. Penso che l'OP stia cercando di capire come strutturare il repository Github e cosa pubblicare su NPM e tutto ciò che hai detto è che possono fare quello che vogliono. L'OP vuole raccomandazioni specifiche su una buona pratica per questa situazione.
jfriend00

@ jfriend00 Non sono d'accordo. Gli ho raccomandato di eseguire la trascrizione e di pubblicare solo i file necessari per il require( 'package' )funzionamento. Modificherò la mia risposta per renderlo più chiaro. Detto questo, la risposta di Jose è molto meglio della mia.
JoshWillik,

La risposta di José è molto buona, ma apprezzo questa per aver delineato esplicitamente buone regole pratiche per quando / perché usare uno contro due pacchetti.
Jordan Gray,

3

La chiave principale in package.jsondecide il punto di ingresso al pacchetto una volta pubblicato. Quindi puoi mettere l'output di Babel dove vuoi e devi solo menzionare il percorso giusto in mainchiave.

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

Ecco un articolo ben scritto su come pubblicare un pacchetto npm

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

Ecco un repository di esempio che puoi utilizzare come riferimento

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


Quel post omette il fatto che puoi (e dovresti) pubblicare su moduli NPM creati in ES6 e in importgrado di farlo direttamente senza richiedere Babel o altri transpiler.
Dan Dascalescu,

0

A seconda dell'anatomia del modulo, questa soluzione potrebbe non funzionare, ma se il modulo è contenuto in un singolo file e non ha dipendenze (non utilizza l' importazione ), utilizzando il modello seguente è possibile rilasciare il codice così com'è e potrà essere importato con importazione (moduli Browser ES6) e richiedere (moduli CommonJS nodo)

Come bonus, sarà possibile importare usando un elemento 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 ;
})()

my-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 */
    }

Per usare il tuo modulo, ora puoi usare la sintassi normale ...

Se importato in NODE ...

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

Se importato in BROWSER ...

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

O anche se incluso utilizzando un elemento Script HTML ...

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

-1

Qualche nota in più per chiunque, usando i propri moduli direttamente da Github, senza essere pubblicati moduli :

Il gancio "prepublish" ( ampiamente usato ) non sta facendo nulla per te.

La cosa migliore che si può fare (se si prevede di fare affidamento su repository github, non su materiale pubblicato):

  • non elencati srcda .npmignore (in altre parole: permetterlo). Se non ne hai uno .npmignore, ricorda: una copia di .gitignoreverrà utilizzata invece nella posizione installata, comels node_modules/yourProject mostrato.
  • assicurarsi, babel-cli è una dipendenza dal tuo modulo, non solo un devDepenceny poiché stai effettivamente costruendo sulla macchina consumatrice, cioè sul computer degli sviluppatori di app, che sta usando il tuo modulo
  • fai il build, nel gancio di installazione, cioè:

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

(nessun valore aggiunto nel provare qualcosa di "preinstallato", ad esempio potrebbe mancare babel-cli)


3
La compilazione all'installazione è molto scortese. Per favore non farlo - il tempo di installazione di npm è abbastanza male! Per il codice interno in cui si desidera evitare di utilizzare un repository di pacchetti npm, è possibile: 1) utilizzare un monorepo, 2) caricare e dipendere npm packdall'output, 3) verificare l'output di build.
Simon Buchan,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.