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.