Como publicar um módulo escrito no ES6 para o NPM?

144

Eu estava prestes a publicar um módulo no NPM, quando pensei em reescrevê-lo no ES6, para prepará-lo para o futuro e aprender o ES6. Eu usei o Babel para transpilar para o ES5 e executar testes. Mas não tenho certeza de como proceder:

  1. Transpile e publico a pasta resultante / out no NPM?
  2. Incluo a pasta de resultados no meu repositório do Github?
  3. Ou mantenho 2 repositórios, um com o código ES6 + script gulp para o Github e outro com os resultados transpilados + testes para o NPM?

Resumindo: quais etapas eu preciso seguir para publicar um módulo escrito no ES6 para o NPM, enquanto ainda permito que as pessoas navegem / bifurcem o código original?

Travelling Tech Guy
fonte
Ultimamente tenho lutado com essa decisão. Estou vendo a resposta que você marcou como correta, sendo José também o consenso.
talves
Aqui está a minha resposta 2018 , tendo em conta o progresso, com o apoio módulo desde 2015.
Dan Dascalescu
1
Eu adoraria se pudesse fazer o oposto. Use um módulo ES para importar um módulo NPM, mas estes são os únicos resultados que recebo.
SeanMC 12/03/19

Respostas:

81

O padrão que eu vi até agora é manter os arquivos es6 em um srcdiretório e criar suas coisas na pré-publicação do npm no libdiretório

Você precisará de um arquivo .npmignore, semelhante ao .gitignore, mas ignorando em srcvez de lib.

José F. Romaniello
fonte
4
Você tem repositório de exemplo?
Ahmed Abbas
2
@JamesAkwuh Note que você provavelmente vai querer mudar o "start" e comandos "construir" na package.json usar o caminho relativo do babel-cli: ./node_modules/babel-cli/bin/babel.js -s inline -d lib -w src. Isso deve garantir que as instalações não falhem ao implantar em novos ambientes.
precisa saber é o seguinte
2
@phazonNinja alças NPM-lo
James Akwuh
4
"Se não houver arquivo .npmignore, mas houver um arquivo .gitignore, o npm ignorará os itens correspondentes ao arquivo .gitignore." docs oficiais da NPM
Frank
10
Em vez de .npmignorevocê pode usar o filescampo em package.json . Permite especificar exatamente os arquivos que você deseja publicar, em vez de procurar arquivos aleatórios que não deseja publicar.
Dan Dascalescu
76

Eu gosto da resposta do José. Notei que vários módulos já seguem esse padrão. Veja como você pode implementá-lo facilmente com o Babel6. Eu instalo babel-clilocalmente para que a compilação não seja interrompida se eu mudar minha versão global do babel.

.npmignore

/src/

.gitignore

/lib/
/node_modules/

Instale 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"]
  }
}
Marius Craciunoiu
fonte
29
Quaisquer comandos no scriptsserão node_modules/.binadicionados aos seus $PATHe, como babel-cliinstala um binário, node_modules/.bin/babelnão há necessidade de referenciar o comando pelo caminho.
Sukima 30/12/2015
3
Por favor note que prepublishé problemática porque poderia correr no momento da instalação ( github.com/npm/npm/issues/3059 ), preferem o mais idiomática versiongancho de roteiro ( docs.npmjs.com/cli/version )
mattecapu
@mattecapu parece que o problema prepublishainda está lá. No momento, penso que compila manualmente o srcdiretório e npm publishé o caminho a percorrer.
sonlexqt
1
Você pode usar o prepublishOnlygancho de script (consulte docs.npmjs.com/misc/scripts#prepublish-and-prepare ). Observe que na versão 5 do npm isso deve funcionar conforme o esperado, mas por enquanto (supondo que você esteja usando o npm v4 +) isso deve funcionar.
Alex Mann
1
O @FrankNocke prepublishé executado antes publish(obv.), Que envia as coisas para npm (ou onde quer que você configure). Então é para a construção o que se passa no pacote NPM, mesmo se não for o check-in.
Simon Buchan
42

TL; DR - não, até ~ outubro de 2019. O Node.js Módulos equipe tem perguntou :

Não publique nenhum pacote de módulo ES destinado ao Node.js. até [outubro de 2019]

Atualização de maio de 2019

Desde 2015, quando essa pergunta foi feita, o suporte a JavaScript para módulos amadureceu significativamente e, espero, permanecerá oficialmente estável em outubro de 2019. Todas as outras respostas agora estão obsoletas ou muito complicadas. Aqui está a situação atual e as melhores práticas.

Suporte ES6

99% do ES6 (também conhecido como 2015) é suportado pelo Node desde a versão 6 . A versão atual do Node é 12. Todos os navegadores perenes suportam a grande maioria dos recursos do ES6. O ECMAScript está agora na versão 2019 , e o esquema de versão agora favorece o uso de anos.

Módulos ES (também conhecidos como módulos ECMAScript) nos navegadores

Todos os verdes navegadores foram apoiar import -ing módulos ES6 desde 2017. importações dinâmicos são suportados pelo Chrome (+ garfos como Opera e Samsung Internet) e Safari. O suporte ao Firefox está previsto para a próxima versão, 67.

Você não precisa mais do Webpack / rollup / Parcel etc. para carregar os módulos. Eles ainda podem ser úteis para outros fins, mas não são necessários para carregar seu código. Você pode importar diretamente URLs apontando para o código dos módulos ES.

Módulos ES no Nó

Os módulos ES ( .mjsarquivos com import/ export) são suportados desde o Nó v8.5.0 chamando nodecom o --experimental-modulessinalizador. O nó v12, lançado em abril de 2019, reescreveu o suporte aos módulos experimentais. A mudança mais visível é que a extensão do arquivo precisa ser especificada por padrão ao importar:

// lib.mjs 

export const hello = 'Hello world!';

// index.mjs:

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

Observe as .mjsextensões obrigatórias por toda parte. Correr como:

node --experimental-modules index.mjs

O lançamento do nó 12 é também quando a Equipe de Módulos perguntou desenvolvedores de não publicar pacotes de módulos ES destinado ao uso por Node.js até que uma solução seja encontrada para o uso de pacotes via tanto require('pkg')e import 'pkg'. Você ainda pode publicar módulos ES nativos destinados a navegadores.

Suporte ao ecossistema de módulos ES nativos

Em maio de 2019, o suporte ao ecossistema para os Módulos ES é imaturo. Por exemplo, estruturas de teste como Jest e Ava não suportam --experimental-modules. Você precisa usar um transpiler e, em seguida, deve decidir entre usar a import { symbol }sintaxe import ( ) nomeada (que ainda não funcionará com a maioria dos pacotes npm) e a sintaxe padrão de importação ( import Package from 'package'), que funciona, mas não quando Babel a analisa. para pacotes criados no TypeScript (graphql-tools, node-influx, faast etc.) No entanto, existe uma solução alternativa que funciona com --experimental-modulese se Babel transpila seu código para que você possa testá-lo com Jest / Ava / Mocha etc:

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

Indiscutivelmente feio, mas dessa maneira você pode escrever seu próprio código de módulo ES com import/ exporte executá-lo com node --experimental-modules, sem transpilers. Se você tiver dependências que ainda não estão preparadas para o ESM, importe-as como acima, e poderá usar estruturas de teste e outras ferramentas via Babel.


Resposta anterior à pergunta - lembre-se, não faça isso até o Node resolver o problema de solicitação / importação, esperamos que em outubro de 2019.

Publicando módulos ES6 para npm, com compatibilidade com versões anteriores

Para publicar um módulo ES no npmjs.org para que ele possa ser importado diretamente, sem Babel ou outros transpilers, basta apontar o maincampo package.jsonpara o .mjsarquivo, mas omita a extensão:

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

Essa é a única mudança. Ao omitir a extensão, o Node procurará primeiro um arquivo mjs se for executado com --experimental-modules. Caso contrário, ele retornará ao arquivo .js, portanto, seu processo de transpilação existente para suportar versões mais antigas do Node funcionará como antes - apenas aponte Babel para o .mjs(s) arquivo (s).

Aqui está a fonte de um módulo ES nativo com compatibilidade com versões anteriores para o Nó <8.5.0 que publiquei no NPM. Você pode usá-lo agora mesmo, sem Babel ou qualquer outra coisa.

Instale o módulo:

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

Crie um arquivo de teste test.mjs :

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

Execute o nó (v8.5.0 +) com o sinalizador --experimental-modules:

node --experimental-modules test.mjs

TypeScript

Se você desenvolver no TypeScript, poderá gerar o código ES6 e usar os módulos ES6:

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

Em seguida, é necessário renomear a *.jssaída para .mjs, um problema conhecido que, esperançosamente, será corrigido em breve para tscque os .mjsarquivos sejam gerados diretamente.

Dan Dascalescu
fonte
3
Dizendo "Todos os navegadores sempre verdes suportam a grande maioria dos recursos do ES6". não significa muito quando você olha para o dados e percebe que o suporte es6 nos navegadores atinge apenas cerca de 80% de todos os usuários.
Pedro Pedrosa
3
Atualmente, o ecossistema definitivamente não está maduro o suficiente para isso. A equipe do Node.js. com o lançamento da v12 perguntou especificamente: "Por favor, não publique nenhum pacote de módulo ES destinado ao Node.js. até que isso seja resolvido." 2ality.com/2019/04/nodejs-esm-impl.html#es-modules-on-npm O Mocha não suporta originalmente arquivos .mjs. Muitas bibliotecas (por exemplo, create-react-app, react-apollo, graphql-js) tiveram problemas com dependências que contêmmjs arquivos. O Node.js planeja lançar o suporte oficial em outubro de 2019, que é o primeiro que eu revisitaria seriamente.
Thisismydesign
3
@thisismydesign: obrigado pelo lembrete para atualizar esta resposta antiga! Apenas fiz isso.
Dan Dascalescu 20/05/19
17

@Jose está certo. Não há nada de errado em publicar o ES6 / ES2015 no NPM, mas isso pode causar problemas, especialmente se a pessoa que usa o seu pacote estiver usando o Webpack, por exemplo, porque normalmente as pessoas ignoram a node_modulespasta durante o pré-processamento babelpor motivos de desempenho.

Então, é só usar gulp, gruntou simplesmente Node.js para construir uma libpasta que é ES5.

Aqui está o meu build-lib.jsscript, que eu mantenho ./tools/(não gulpou gruntaqui):

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

Aqui está um último conselho: você precisa adicionar um .npmignore ao seu projeto . Se npm publishnão encontrar esse arquivo, ele será usado .gitignore, o que causará problemas, porque normalmente o .gitignorearquivo será excluído ./libe incluído ./src, exatamente o oposto do que você deseja ao publicar no NPM. o.npmignore arquivo tem basicamente a mesma sintaxe de .gitignore(AFAIK).

André Pena
fonte
1
Em vez de .npmignorevocê pode usar o filescampo em package.json . Permite especificar exatamente os arquivos que você deseja publicar, em vez de procurar arquivos aleatórios que não deseja publicar.
Dan Dascalescu
Isso não vai quebrar as árvores?
Joe
Eu não recomendo usar .npmignore, tente package.json, em filesvez disso, consulte: github.com/c-hive/guides/blob/master/js/…
thisismydesign
@thisismydesign: foi exatamente o que eu havia recomendado no meu comentário acima ..?
Dan Dascalescu 20/05/19
My bad, não notou :)
thisismydesign
8

Seguindo a abordagem de José e Marius, (com atualização da versão mais recente de Babel em 2019): Mantenha os arquivos JavaScript mais recentes em um diretório src e construa com o prepublishscript e a saída do npm no diretório lib.

.npmignore

/src

.gitignore

/lib
/node_modules

Instale o Babel (versão 7.5.5 no meu 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 eu tenho src/index.jsque usa a função de seta:

"use strict";

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

Aqui está o repositório no GitHub .

Agora você pode publicar o pacote:

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

Successfully compiled 1 file with Babel.
...

Antes que o pacote seja publicado no npm, você verá que lib/index.jsfoi gerado, que é transpilado para es5:

"use strict";

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

NewOneWithParameters(10, 20);

[Atualização para empacotador de rollup]

Conforme solicitado pelo @kyw, como você integraria o empacotador de Rollup?

Primeiro, instale rollup erollup-plugin-babel

npm install -D rollup rollup-plugin-babel

Segundo, crie rollup.config.js no diretório raiz do projeto

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

Por fim, atualize prepublish empackage.json

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

Agora você pode correr npm publish e, antes que o pacote seja publicado no npm, você verá que lib / index.js foi gerado, transpilado para es5:

'use strict';

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

NewOneWithParameters(10, 20);

Nota: a propósito, você não precisa mais @babel/clise estiver usando o empacotador de Rollup. Você pode desinstalá-lo com segurança:

npm uninstall @babel/cli
Yuci
fonte
Como você integraria o empacotador de Rollup?
Kyw #
1
@kyw, sobre como integrar o empacotador de Rollup, veja minha resposta atualizada.
Yuci
Atualização de dezembro de 2019 -> github.com/rollup/rollup/blob/…
a.barbieri
6

Se você quiser ver isso em ação em um módulo Node de código aberto pequeno e muito simples, dê uma olhada no enésimo dia (que eu iniciei - também em outros colaboradores). Procure no arquivo package.json e na etapa de pré-publicação, que o levará a onde e como fazer isso. Se você clonar esse módulo, poderá executá-lo localmente e usá-lo como modelo para você.

Cara
fonte
4

O Node.js. 13.2.0+ suporta o ESM sem o sinalizador experimental e existem algumas opções para publicar pacotes NPM híbridos (ESM e CommonJS) (dependendo do nível de compatibilidade com versões anteriores necessário): https://2ality.com/2019 /10/hybrid-npm-packages.html

Eu recomendo seguir a maneira completa de compatibilidade com versões anteriores para facilitar o uso do seu pacote. Isso pode ter a seguinte aparência:

O pacote híbrido possui os seguintes arquivos:

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

Importando do CommonJS:

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

Importando do ESM:

import {x} from 'mypkg/esm';

Fizemos uma investigação sobre o suporte ao ESM em 05.2019 e descobrimos que muitas bibliotecas estavam sem suporte (daí a recomendação para compatibilidade com versões anteriores):

thisismydesign
fonte
Não consigo importar módulos ES6 instalados globalmente na pasta node_modules (fornecida pelo npm root -g). Nós realmente não devemos ser capazes de fazer isso? Estou realmente confuso. Eu sei que o link npm pode resolver o problema vinculando o módulo à minha pasta node_modules local, mas quero saber por que a importação de módulos de nó global não é suportada.
Joakim L. Christiansen
Respondendo a mim mesmo, acho que nunca será suportado: github.com/nodejs/node-eps/blob/master/… É uma decisão realmente estúpida, porém, seria fácil de apoiar ...
Joakim L. Christiansen
3

Os dois critérios de um pacote NPM é que ele é utilizável com nada mais que require( 'package' )ae faz algo que é software-ish.

Se você cumprir esses dois requisitos, poderá fazer o que quiser. Mesmo que o módulo seja escrito no ES6, se o usuário final não precisar saber disso, eu o transpilaria por enquanto para obter o máximo suporte.

No entanto, se, como o koa , seu módulo exigir compatibilidade com usuários que usam os recursos do ES6, talvez a solução com dois pacotes seja uma idéia melhor.

Leve embora

  1. Publique apenas o código necessário para fazer o require( 'your-package' )trabalho.
  2. A menos que o ES5 e 6 seja importante para o usuário, publique apenas 1 pacote. Transpile se necessário.
JoshWillik
fonte
1
Isso não parece responder à pergunta. Acho que o OP está tentando descobrir como estruturar o repositório Github e o que publicar no NPM e tudo o que você disse é que eles podem fazer o que quiserem. O PO deseja recomendações específicas sobre boas práticas para esta situação.
usar o seguinte código
@ jfriend00 Eu discordo. Eu recomendei que ele transpile e publique apenas os arquivos necessários para o require( 'package' )trabalho. Vou editar minha resposta para deixar isso mais claro. Dito isto, a resposta de Jose é muito melhor que a minha.
precisa saber é o seguinte
A resposta de José é muito boa, mas agradeço esta por descrever explicitamente boas regras práticas para quando / por que usar um vs. dois pacotes.
Jordan Gray
3

A chave principal package.jsondecide o ponto de entrada para o pacote, uma vez publicado. Assim, você pode colocar a saída do seu Babel onde quiser e apenas mencionar o caminho certo na mainchave.

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

Aqui está um artigo bem escrito sobre como publicar um pacote npm

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

Aqui está um exemplo de repositório que você pode usar como referência

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

Dinesh Pandiyan
fonte
Essa publicação omite o fato de que você pode (e deve) publicar nos módulos NPM criados no ES6 e importcapazes diretamente, sem a necessidade de Babel ou outros transpiladores.
Dan Dascalescu 26/09/18
0

Dependendo da anatomia do seu módulo, esta solução pode não funcionar, mas se o seu módulo estiver contido em um único arquivo e não tiver dependências (não utiliza importação ), usando o padrão a seguir, você pode liberar seu código como está. , e poderá ser importado com a importação (Módulos do Navegador ES6) e exigir (Módulos CommonJS do Nó)

Como bônus, será possível importar com o uso de um 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 */
    }

Para usar seu módulo, agora você pode usar a sintaxe regular ...

Quando importado no NODE ...

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

Quando importado no BROWSER ...

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

Ou mesmo quando incluído usando um elemento de script HTML ...

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

Algumas notas extras para qualquer pessoa, usando módulos próprios diretamente do github, sem passar pelos módulos publicados :

O gancho "pré-publicar" ( amplamente utilizado ) não está fazendo nada por você.

A melhor coisa que se pode fazer (se planeja confiar nos repositórios do github, não nas coisas publicadas):

  • itens não listados srca partir .npmignore (em outras palavras: permitir que ele). Se você não tiver um .npmignore, lembre-se: Uma cópia .gitignoreserá usada no local instalado, comols node_modules/yourProject será mostrado.
  • certifique-se de que babel-clié uma dependência do seu módulo, não apenas uma devDepenceny, pois você está realmente desenvolvendo a máquina consumidora, também conhecida no computador dos desenvolvedores de aplicativos, que está usando o seu módulo
  • faça o build, no gancho de instalação, ou seja:

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

(nenhum valor agregado ao tentar algo "pré-instalação", ou seja, pode estar faltando babel-cli)

Frank Nocke
fonte
3
Compilar na instalação é muito rude. Por favor, não faça isso - o tempo de instalação das npm já é ruim o suficiente! Para código interno em que você deseja evitar o uso de um repositório de pacotes npm, você pode: 1) usar um monorepo, 2) Carregar e depender da npm packsaída, 3) verificar a saída da compilação.
Simon Buchan