Как опубликовать модуль, написанный на ES6, в NPM?

149

Я собирался опубликовать модуль в NPM, когда подумал о том, чтобы переписать его на ES6, чтобы сделать его перспективным и изучить ES6. Я использовал Babel для переноса на ES5 и запуска тестов. Но я не знаю, что делать дальше:

  1. Могу ли я транспилировать и публиковать полученную папку в NPM?
  2. Могу ли я включить папку результатов в репозиторий Github?
  3. Или я могу поддерживать 2 репозитория, один с кодом ES6 + скрипт gulp для Github, а другой с переданными результатами + тестами для NPM?

Вкратце: какие шаги мне нужно предпринять, чтобы опубликовать модуль, написанный на ES6, в NPM, при этом позволяя людям просматривать / разветвлять исходный код?

Путешественник Tech Guy
источник
В последнее время я борюсь с этим решением. Я вижу, что ответ, который вы отметили как правильный, Хосе также придерживается консенсуса.
Talves
Вот мой ответ 2018 года с учетом прогресса в поддержке модулей с 2015 года.
Дэн Даскалеску,
1
Я был бы рад, если бы мог сделать наоборот. Используйте модуль ES для импорта модуля NPM, но это единственные результаты, которые я получаю.
SeanMC

Ответы:

85

Шаблон, который я видел до сих пор, заключается в том, чтобы хранить файлы es6 в srcкаталоге и создавать свои материалы в предварительной публикации npm в libкаталоге.

Вам понадобится файл .npmignore, похожий на .gitignore, но игнорирующий srcвместо lib.

Хосе Ф. Романиелло
источник
5
У вас есть репозиторий примеров?
Ахмед Аббас
2
@JamesAkwuh Обратите внимание , что вы, вероятно , захотите изменить «Пуск» и команду «строить» в package.json использовать относительный путь Вавилонского-кли: ./node_modules/babel-cli/bin/babel.js -s inline -d lib -w src. Это должно гарантировать, что установка не завершится ошибкой при развертывании в новых средах.
phazonNinja
2
@phazonNinja npm справится с этим
Джеймс Акву
4
«Если файла .npmignore нет, но есть файл .gitignore, то npm будет игнорировать материал, соответствующий файлу .gitignore». официальные документы npm
Фрэнк
11
Вместо этого .npmignoreвы можете использовать filesполе в package.json . Он позволяет вам точно указать файлы, которые вы хотите опубликовать, вместо того, чтобы искать случайные файлы, которые вы не хотите публиковать.
Дэн Даскалеску
80

Мне нравится ответ Хосе. Я уже заметил, что несколько модулей следуют этому шаблону. Вот как вы можете легко реализовать это с помощью Babel6. Я устанавливаю babel-cliлокально, чтобы сборка не сломалась, если я когда-нибудь изменю свою глобальную версию babel.

.npmignore

/src/

.gitignore

/lib/
/node_modules/

Установить 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
К ним scriptsбыли node_modules/.binдобавлены любые команды в воле, $PATHи, поскольку babel-cliустанавливает двоичный файл node_modules/.bin/babel, нет необходимости ссылаться на команду по пути.
Sukima
3
Обратите внимание, что prepublishэто проблематично, потому что он может работать во время установки ( github.com/npm/npm/issues/3059 ), предпочитайте более идиоматический versionскриптовый перехватчик ( docs.npmjs.com/cli/version )
mattecapu
@mattecapu похоже, что проблема prepublishвсе еще существует. На данный момент я думаю, что вручную скомпилируйте srcкаталог, и npm publishэто путь.
sonlexqt
1
Вы можете использовать prepublishOnlyперехватчик сценария (см. Docs.npmjs.com/misc/scripts#prepublish-and-prepare ). Обратите внимание, что в версии 5 npm это должно работать, как ожидалось, но пока (при условии, что вы используете npm v4 +) это должно работать.
Alex Mann
1
@FrankNocke prepublishзапускается до publish(obv.), Который отправляет данные в npm (или куда вы настраиваете). Так что это для создания того, что входит в пакет NPM, даже если он не зарегистрирован.
Саймон Бьюкен
44

TL; DR - Не до ~ октября 2019 года Node.js модули Team не спросил :

Не публикуйте пакеты модулей ES, предназначенные для использования Node.js, до [октября 2019 г.]

Обновление за май 2019 г.

С 2015 года, когда был задан этот вопрос, поддержка модулей JavaScript значительно выросла и, надеюсь, станет официально стабильной в октябре 2019 года. Все остальные ответы теперь устарели или чрезмерно сложны. Вот текущая ситуация и лучшие практики.

Поддержка ES6

99% ES6 (также известный как 2015) поддерживаются Node с версии 6 . Текущая версия Node - 12. Все вечнозеленые браузеры поддерживают подавляющее большинство функций ES6. ECMAScript сейчас находится в версии 2019 , а схема управления версиями теперь поддерживает использование лет.

Модули ES (также известные как модули ECMAScript) в браузерах

Все вечнозеленые браузеры были поддерживать import -ную модули ES6 поскольку 2017. Динамические импорта будут поддерживаться с помощью Chrome (+ вилы , как Opera и Samsung Интернет) и Safari. Поддержка Firefox намечена на следующую версию, 67.

Вам больше не нужно Webpack / rollup / Parcel и т. Д. Для загрузки модулей. Они могут быть полезны для других целей, но не требуются для загрузки вашего кода. Вы можете напрямую импортировать URL-адреса, указывающие на код модулей ES.

Модули ES в Node

Модули ES ( .mjsфайлы с import/ export) поддерживаются с Node v8.5.0 путем вызова nodeс --experimental-modulesфлагом. Node v12, выпущенный в апреле 2019 года, переписал поддержку экспериментальных модулей. Наиболее заметным изменением является то, что расширение файла необходимо указывать по умолчанию при импорте:

// lib.mjs 

export const hello = 'Hello world!';

// index.mjs:

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

Обратите внимание на обязательные .mjsрасширения повсюду. Беги как:

node --experimental-modules index.mjs

В выпуске Node 12 команда Modules попросила разработчиков не публиковать пакеты модулей ES, предназначенные для использования Node.js, до тех пор, пока не будет найдено решение для использования пакетов через require('pkg')и import 'pkg'. Вы по-прежнему можете публиковать собственные модули ES, предназначенные для браузеров.

Экосистемная поддержка собственных модулей ES

По состоянию на май 2019 года экосистемная поддержка модулей ES еще не развита. Например, такие тестовые среды, как Jest и Ava , не поддерживают --experimental-modules. Вам нужно использовать транспилятор, а затем выбрать между использованием именованного import { symbol }синтаксиса import ( ) (который пока не работает с большинством пакетов npm) и синтаксиса импорта по умолчанию ( import Package from 'package'), который работает, но не тогда, когда Babel его анализирует для пакетов, созданных на TypeScript (graphql-tools, node-influenx, faast и т. д.). Однако существует обходной путь, который работает как с, так --experimental-modulesи если Babel транспилирует ваш код, чтобы вы могли протестировать его с помощью Jest / Ava / Mocha и т. д .:

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

Возможно, некрасиво, но таким образом вы можете написать свой собственный код модулей ES с import/ exportи запускать его node --experimental-modulesбез транспиляторов. Если у вас есть зависимости, которые еще не готовы к ESM, импортируйте их, как указано выше, и вы сможете использовать тестовые среды и другие инструменты через Babel.


Предыдущий ответ на вопрос - помните, не делайте этого, пока Node не решит проблему require / import, надеюсь, примерно в октябре 2019 года.

Публикация модулей ES6 в npm с обратной совместимостью

Для того, чтобы опубликовать модуль ES в npmjs.org так , что он может быть импортирован непосредственно, без Бабеля и других transpilers, просто указать mainполе в package.jsonв .mjsфайл, но опустить расширение:

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

Это единственное изменение. Если опустить расширение, Node будет сначала искать файл mjs при запуске с --experimental-modules. В противном случае он вернется к файлу .js, поэтому ваш существующий процесс транспиляции для поддержки более старых версий Node будет работать, как и раньше - просто убедитесь, что указали Babel на .mjsфайл (ы).

Вот исходный код для собственного модуля ES с обратной совместимостью для Node <8.5.0, который я опубликовал в NPM. Вы можете использовать его прямо сейчас, без Babel или чего-либо еще.

Установите модуль:

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

Создайте тестовый файл test.mjs :

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

Запустите узел (v8.5.0 +) с флагом --experimental-modules:

node --experimental-modules test.mjs

Машинопись

Если вы разрабатываете на TypeScript, вы можете сгенерировать код ES6 и использовать модули ES6:

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

Затем вам нужно переименовать *.jsвывод в .mjsизвестную проблему, которая, надеюсь, скоро будет исправлена, чтобы tscможно было .mjsнапрямую выводить файлы.

Дэн Даскалеску
источник
3
Сказать: «Все вечнозеленые браузеры поддерживают подавляющее большинство функций ES6». не имеет большого значения, если вы посмотрите на данные и поймете, что поддержка es6 в браузерах достигает только 80% всех пользователей.
Педро Педроса
3
В настоящее время экосистема явно недостаточно зрелая для этого. Команда Node.js при выпуске v12 специально просила: «Пожалуйста, не публикуйте никакие пакеты модулей ES, предназначенные для использования Node.js, пока проблема не будет решена». 2ality.com/2019/04/nodejs-esm-impl.html#es-modules-on-npm Mocha изначально не поддерживает файлы .mjs. Многие-многие библиотеки (например, create-react-app, react-apollo, graphql-js) имели проблемы с зависимостями, содержащими mjsфайлы. Node.js планирует развернуть официальную поддержку в октябре 2019 года, что является первым делом, на которое я бы серьезно вернулся.
thisismydesign
3
@thisismydesign: спасибо за напоминание об обновлении этого старого ответа! Только что так и сделал.
Дэн Даскалеску
17

@ Хосе прав. Нет ничего плохого в публикации ES6 / ES2015 в NPM, но это может вызвать проблемы, особенно если человек, использующий ваш пакет, использует, например, Webpack, потому что обычно люди игнорируют node_modulesпапку во время предварительной обработки из babelсоображений производительности.

Таким образом, просто использовать gulp, gruntили просто Node.js построить libпапку, ES5.

Вот мой build-lib.jsсценарий, который я храню ./tools/(нет gulpили gruntздесь):

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

И последний совет: вам нужно добавить в свой проект .npmignore . Если npm publishэтот файл не найден, он будет использовать .gitignoreвместо него, что вызовет у вас проблемы, потому что обычно ваш .gitignoreфайл будет исключать ./libи включать ./src, что в точности противоположно тому, что вы хотите при публикации в NPM. .npmignoreФайл имеет в основном один и тот же синтаксис .gitignore(AFAIK).

Андре Пена
источник
1
Вместо этого .npmignoreвы можете использовать filesполе в package.json . Он позволяет вам точно указать файлы, которые вы хотите опубликовать, вместо того, чтобы искать случайные файлы, которые вы не хотите публиковать.
Дэн Даскалеску
Разве этот лом не сотрясает дерево?
Джо
Я не рекомендую использовать .npmignore, попробуйте package.json«ы filesвместо этого, см: github.com/c-hive/guides/blob/master/js/...
thisismydesign
@thisismydesign: это именно то, что я рекомендовал в моем комментарии выше ..?
Дэн Даскалеску
Моя
беда
8

Следуя подходу Хосе и Мариуса (с обновлением последней версии Babel в 2019 году): храните последние файлы JavaScript в каталоге src и выполняйте сборку с помощью npm prepublish сценария и выводите в каталог lib.

.npmignore

/src

.gitignore

/lib
/node_modules

Установите Babel (в моем случае версия 7.5.5)

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

И у меня есть src/index.jsфункция стрелки:

"use strict";

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

Вот репо на GitHub .

Теперь вы можете опубликовать пакет:

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

Successfully compiled 1 file with Babel.
...

Перед публикацией пакета в npm вы увидите, что lib/index.jsон был сгенерирован и передан в es5:

"use strict";

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

NewOneWithParameters(10, 20);

[Обновление для сборщика накопительных пакетов]

На вопрос @kyw, как бы вы интегрировали пакет Rollup?

Сначала установите rollupиrollup-plugin-babel

npm install -D rollup rollup-plugin-babel

Во-вторых, создайте rollup.config.jsв корневом каталоге проекта

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

Наконец, обновите prepublishвpackage.json

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

Теперь вы можете запустить npm publish, и перед публикацией пакета в npm вы увидите, что был сгенерирован lib / index.js, который переносится в es5:

'use strict';

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

NewOneWithParameters(10, 20);

Примечание: кстати, вам больше не нужно, @babel/cliесли вы используете пакет Rollup. Вы можете безопасно удалить его:

npm uninstall @babel/cli
Юси
источник
Как бы вы интегрировали пакет Rollup?
kyw 02
1
@kyw, о том, как интегрировать пакет Rollup, см. мой обновленный ответ.
Юйцы
Обновление за декабрь 2019 года -> github.com/rollup/rollup/blob/…
a.barbieri
6

Если вы хотите увидеть это в действии в очень простом небольшом модуле Node с открытым исходным кодом, взгляните на nth-day (который я начал, а также другие участники). Посмотрите файл package.json и этап предварительной публикации, который приведет вас к тому, где и как это сделать. Если вы клонируете этот модуль, вы можете запустить его локально и использовать в качестве шаблона для себя.

Парень
источник
4

Node.js 13.2.0+ поддерживает ESM без экспериментального флага, и есть несколько вариантов публикации гибридных (ESM и CommonJS) пакетов NPM (в зависимости от необходимого уровня обратной совместимости): https://2ality.com/2019 /10/hybrid-npm-packages.html

Я рекомендую использовать полную обратную совместимость, чтобы упростить использование вашего пакета. Это могло выглядеть так:

В гибридном пакете есть следующие файлы:

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

Импорт из CommonJS:

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

Импорт из ESM:

import {x} from 'mypkg/esm';

Мы провели расследование поддержки ESM в 05.2019 и обнаружили, что многим библиотекам не хватает поддержки (отсюда и рекомендация по обратной совместимости):

это
источник
Кажется, я не могу импортировать глобально установленные модули ES6 в папку node_modules (предоставленную npm root -g). Неужели мы действительно не должны этого делать? Я действительно сбит с толку. Я знаю, что ссылка npm может решить проблему, связав модуль с моей локальной папкой node_modules, но я хочу знать, почему не поддерживается импорт модулей глобального узла.
Йоаким Л. Кристиансен
Отвечая самому себе, я полагаю, что это никогда не будет поддержано: github.com/nodejs/node-eps/blob/master/… Это действительно глупое решение, которое было бы легко поддержать ...
Йоаким Л. Кристиансен
@ JoakimL.Christiansen Namaste. Испытывая аналогичные трудности из-за совершенно неверной официальной позиции сообщества узлов в отношении модулей, я перешел по этой ссылке, и да, я был разочарован, но не удовлетворен, я проверил дату публикации этой связанной статьи ... 9 августа 2017. Просто указание на это, поскольку связанная статья может быть
отвлекающим
3

Два критерия NPM-пакета заключаются в том, что его можно использовать только с a require( 'package' )и делать что-то похожее на программное обеспечение.

Если вы выполните эти два требования, вы сможете делать все, что захотите. Даже если модуль написан на ES6, если конечному пользователю это не нужно знать, я бы сейчас перенес его, чтобы получить максимальную поддержку.

Однако, если нравится Коа , ваш модуль требует совместимости с пользователями, использующими функции ES6, возможно, решение из двух пакетов будет лучшей идеей.

Вынос

  1. Публикуйте ровно столько кода, сколько нужно для require( 'your-package' )работы.
  2. Если для пользователя нет значения между ES5 и 6, опубликуйте только 1 пакет. Если нужно, переместите его.
ДжошВиллик
источник
1
Кажется, это не отвечает на вопрос. Я думаю, что OP пытается выяснить, как структурировать свое репозиторий Github и что публиковать в NPM, и все, что вы как бы сказали, это то, что они могут делать все, что хотят. ОП требует конкретных рекомендаций по передовой практике в этой ситуации.
jfriend00
@ jfriend00 Я не согласен. Я рекомендовал ему транспилировать и публиковать только те файлы, которые необходимы для require( 'package' )работы. Я отредактирую свой ответ, чтобы прояснить это. Тем не менее, ответ Хосе намного лучше моего собственного.
JoshWillik
Ответ Хосе очень хорош, но я ценю его за четкое определение хороших практических правил, когда и зачем использовать один или два пакета.
Jordan Gray
3

Главный ключ package.jsonопределяет точку входа в пакет после его публикации. Таким образом, вы можете разместить свой вывод Babel где угодно, и просто нужно указать правильный путь в mainключе.

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

Вот хорошо написанная статья о том, как опубликовать пакет npm

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

Вот образец репо, который вы можете использовать для справки

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

Динеш Пандиян
источник
В этом посте отсутствует тот факт, что вы можете (и должны) публиковать модули NPM, созданные на ES6 и имеющиеimport возможность напрямую, без использования Babel или каких-либо других транспиляторов.
Дэн Даскалеску
0

В зависимости от анатомии вашего модуля это решение может не работать, но если ваш модуль содержится внутри одного файла и не имеет зависимостей (не использует импорт ), используя следующий шаблон, вы можете выпустить свой код как есть , и его можно будет импортировать с помощью импорта (модули браузера ES6) и при необходимости (модули Node CommonJS)

В качестве бонуса его можно будет импортировать с использованием 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 ;
})()

мой-модуль.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 */
    }

Чтобы использовать свой модуль, теперь вы можете использовать обычный синтаксис ...

При импорте в NODE ...

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

При импорте в БРАУЗЕР ...

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

Или даже при включении с использованием элемента сценария HTML ...

<script src="./main.js"></script>
<script>
     myModule.helloWorld();
    // outputs 'Hello World!'
</script>
colxi
источник
-1

Несколько дополнительных замечаний для всех, кто использует собственные модули прямо из github, не просматривая опубликованные модули:

( Широко используемый ) хук "предварительной публикации" ничего не делает для вас.

Лучшее, что можно сделать (если вы планируете полагаться на репозитории github, а не на опубликованные материалы):

  • исключить srcиз списка .npmignore (другими словами: разрешить). Если у вас его нет .npmignore, помните: .gitignoreвместо этого в установленном месте будет использоваться копия , какls node_modules/yourProject вы увидите.
  • удостовериться, babel-cli это зависимость в вашем модуле, а не только devDepenceny, поскольку вы действительно строите на потребляющей машине, известной как компьютер разработчиков приложений, который использует ваш модуль
  • выполните сборку в ловушке установки, т.е.

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

(ничего не стоит пробовать что-то "предустановленное", т.е. может отсутствовать babel-cli)

Фрэнк Нок
источник
3
Компиляция при установке очень грубая. Пожалуйста, не делайте этого - время установки npm достаточно плохое! Для внутреннего кода, где вы хотите избежать использования репозитория пакетов npm, вы можете: 1) использовать монорепозиторий, 2) загружать и зависеть от npm packвывода, 3) проверять вывод сборки.
Саймон Бьюкен