image

NPM — уникальный репозиторий пакетов из мира JavaScript. В основном здесь те JS библиотеки, которые можно использовать во фронтэнде/в браузере, но есть и серверные для использования в node.js и не только. Если вы программируете на Java и у вас появилась необходимость синтегрироваться с NPM репозиторием, то скорее всего у вас один из двух следующих случаев:

  • Вы пишите Web приложение на одном из Java фреймворков и определенные NPM пакеты необходимы для клиентской стороны
  • У вас Java приложение (например, для Андройда), которому необходимо уметь запрашивать зависимости и сами ресурсы/пакеты из NPM

Давайте посмотрим как это можно сделать в Java.

NPM ресурсы для Web-приложения


У вас есть 2 опции:

  • Запаковать нужные NPM ресурсы внутрь вашего WAR/JAR
  • Использовать CDN для загрузки в runtime нужных ресурсов

Упаковка NPM ресурсов в WAR/JAR


Прежде всего вам надо узнать побольше о такой штуке как WebJars. Она позволяет «отражать» NPM (и не только) пакеты в репозиторий Maven. Таким образом вы можете работать с NPM пакетами как с обычными Java пакетами в Maven. Например, для того чтобы включить ресурсы из всем известного Boostrap в ваш WAR достаточно добавить следующую зависимость в pom.xml:

<dependency>
    <groupId>org.webjars.npm</groupId>
    <artifactId>bootstrap</artifactId>
    <version>4.5.0</version>
</dependency>

WebJars отражает пакеты из NPM в Maven вместе со всеми необходимыми зависимостями, так что подключив один JAR по зависимостям будут подключены и все остальные нужные пакеты.
WebJars так же имеет большой набор библиотек под разные Java фреймворки для того чтобы удобнее было работать с запакованными и подключенными ресурсами. Подробнее читайте в документации.

WebJars — отличное подспорье для любого Java Backend разработчика. Но есть и более легкие альтернативы: упаковка нужных пакетов из NPM с помощью Maven плагинов. Здесь, возможно, не полный список:


Например, чтобы включить пакеты vue и vuex нужных версий с помощью jnpm-maven-plugin добавьте следующие строчки в pom.xml:

<plugin>
    <groupId>org.orienteer.jnpm</groupId>
    <artifactId>jnpm-maven-plugin</artifactId>
    <version>1.0</version>
	<executions>
		<execution>
			<goals>
				<goal>install</goal>
			</goals>
			<configuration>
				<packages>
					<package>vue@2.6.11</package>
					<package>vuex@~3.4.0</package>
				</packages>
			</configuration>
		</execution>
	</executions>
</plugin>

Можно использовать нотацию NPM для определения диапазона нужных версий:

  • Звездочка (*|X|x) — 1.* эквивалентно >=1.0.0 & <2.0.0
  • Тильда (~) — ~1.5 эквивалентно >=1.5.0 & <1.6.0
  • Дефис (-) — 1.0-2.0 эквивалентно >=1.0.0 & <=2.0.0
  • Каретка (^) — ^0.2.3 эквивалентно >=0.2.3 & <0.3.0
  • Частичный диапазон — 1 эквивалентно 1.X or >=1.0.0 & <2.0.0
  • Отрицание — !(1.x) эквивалентно <1.0.0 & >=2.0.0
  • Сложные — ~1.3 | (1.4.* & !=1.4.5) | ~2

Также, вы можете задать какие именно файлы нужно включить из пакетов используя includes и excludes. Например, обычно NPM пакет содержит «скомпиленные» файлы в директории /dist. Другие файлы являются исходниками и врядли будут нужны и полезны внутри Java Web приложения. Чтобы включить только содержимое директории dist/ достаточно добавить следующее в секцию :

<includes>
  <include>dist/*</include>
</includes>

По умолчанию jnpm-maven-plugin запаковывает ресурсы по точно таким же путям как и WebJars. Это позволяет использовать упомянутые выше библиотеки WebJars для разных фреймворков для доступа к ресурсам. Если вам необходим какой-то другой специфичный формат упаковывания, то просьба обратиться к документации.

Использование CDN


Есть множество публично доступных CDN с NPM ресурсами. Наиболее известные и используемые:


Также вы можете использовать свой собственный CDN (например поднятый через docker) или даже встроить CDN функционал внутрь вашего Web-App. Например, добавьте следующий сервлет в web.xml чтобы включить JNPM CDN. Отредактируйте по необходимости:

<servlet>
  <servlet-name>CDNServlet</servlet-name>
  <servlet-class>org.orienteer.jnpm.cdn.CDNServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>CDNServlet</servlet-name>
  <url-pattern>/cdn/*</url-pattern>
</servlet-mapping>

После загрузки сервлета NPM ресурсы будут доступны через URL следующего формата: http(s)://<домен>:<порт>/<путь до веб приложения>/cdn/<NPM пакет>/<путь до файла>.
Например:
localhost:8080/cdn/vue@2.6.11/dist/vue.js


Работа с NPM REST API из Java


Безусловно, вы можете использовать напрямую REST API реестра NPM, скажем, через Retrofit. В этом вам поможет соответствующая документация. Но удобнее использовать библиотеку JNPM, которая представляет Java обертку для данного REST API и не только.

Включаем JNPM Jar в pom.xml:

<dependency>
    <groupId>org.orienteer.jnpm</groupId>
    <artifactId>jnpm</artifactId>
    <version>1.0</version>
</dependency>

Инициализируем JNPM API:

JNPMService.configure(JNPMSettings.builder()
  .homeDirectory(Paths.get("/home/myuser/.jnpm")) //Опционально
  .downloadDirectory(Paths.get("/tmp")) //Опционально
  //Другие возможные опции - см. документацию
 	.build());

JNPM API предоставляет 2 опции: Синхронное API и Ассинхронное API через RXJava. Что именно использовать — решать вам:

JNPMService jnpmService = JNPMService.instance(); //Synchronous Java API
RxJNPMService rxJnpmService = JNPMService.instance().getRxService() //RXJava API

Пример использования:

//Общая информаци о NPM реестре
System.out.println(JNPMService.instance().getRegistryInfo());
//Заполучить и напечатать информацию о последней версии VUE
System.out.println(JNPMService.instance().getPackageInfo("vue").getLatest());
//Напечатать описание пакета vue@2.6.11
System.out.println(JNPMService.instance().getVersionInfo("vue", "2.6.11").getDescription());
//Напечатать последнюю версию до второго официального релиза
System.out.println(JNPMService.instance().bestMatch("vue@<2").getVersionAsString());
//Скачать архив для vue@2.6.11 и напечатать локальный путь
VersionInfo vueVersion = JNPMService.instance().getVersionInfo("vue", "2.6.11");
vueVersion.downloadTarball().blockingAwait();
System.out.println(vueVersion.getLocalTarball().getAbsolutePath());
//Искать "vue" и напечатать описание первого результата
System.out.println(JNPMService.instance().search("vue").getObjects().get(0).getSearchPackage().getDescription());
// Пройтись и напечатать информацию по всем dev зависимостям последней версии vue 
// а так же установить данные пакеты так как делает это сам NPM (node_modules/vue и т.д.)
JNPMService.instance().getRxService()
   .traverse(TraverseDirection.WIDER, TraversalRule.DEV_DEPENDENCIES, "vue")
   .subscribe(t -> {System.out.println(t); t.install(Paths.get("target", "readme"), InstallationStrategy.NPM);});

Если у вас какой-то специфичный случай, который был не описан здесь — пожалуйста, дайте знать!