Использование Scala.js с NPM и Browserify

Опубликовано: 2022-03-11

Если вы используете Scala.js, компилятор языка Scala в JavaScript, стандартное управление зависимостями Scala.js может показаться вам слишком ограничивающим в современном мире JavaScript. Scala.js управляет зависимостями с помощью WebJars, а разработчики JavaScript управляют зависимостями с помощью NPM. Поскольку зависимости, созданные NPM, являются серверными, обычно требуется дополнительный шаг с использованием Browserify или Webpack для создания кода браузера.

В этом посте я опишу, как интегрировать Scala.js с множеством модулей JavaScript, доступных в NPM. Вы можете проверить этот репозиторий GitHub для рабочего примера методов, описанных здесь. Используя этот пример и прочитав этот пост, вы сможете собрать свои библиотеки JavaScript с помощью NPM, создать пакет с помощью Browserify и использовать результат в своем собственном проекте Scala.js. И все это даже без установки Node.js, так как всем управляет SBT.

Browserify и Scala.js

Магия Browserify прекрасно работает и в мире Java!
Твитнуть

Управление зависимостями для Scala.js

Сегодня написание приложений на языках, которые компилируются в JavaScript, становится очень распространенной практикой. Все больше и больше людей переходят на расширенные языки JavaScript, такие как CoffeeScript или TypeScript, или транспиляторы, такие как Babel, которые позволяют вам использовать ES6 уже сегодня. В то же время Google Web Toolkit (компилятор из Java в JavaScript) в основном используется для корпоративных приложений. По этим причинам, как разработчик Scala, я больше не считаю использование Scala.js странным выбором. Компилятор работает быстро, создаваемый код эффективен, и в целом это просто способ использовать один и тот же язык как на интерфейсе, так и на сервере.

Тем не менее, использование инструментов Scala в мире JavaScript еще не на 100% естественно. Иногда вам нужно заполнить пробел между экосистемой JavaScript и экосистемой Scala. Scala.js как язык отлично совместим с JavaScript. Поскольку Scala.js — это компилятор языка Scala в JavaScript, очень легко связать код Scala с существующим кодом JavaScript. Самое главное, Scala.js дает вам возможность создавать типизированные интерфейсы (или фасады) для доступа к нетипизированным библиотекам JavaScript (аналогично тому, что вы делаете с TypeScript). Для разработчиков, привыкших к строго типизированным языкам, таким как Java, Scala или даже Haskell, JavaScript слишком слабо типизирован. Если вы являетесь таким разработчиком, вероятно, основная причина заключается в том, что вы можете захотеть использовать Scala.js, чтобы получить (строго) типизированный язык поверх нетипизированного языка.

Проблема в стандартной цепочке инструментов Scala.js, которая основана на SBT и все еще остается открытой, заключается в следующем: как включить зависимости, такие как дополнительные библиотеки JavaScript, в ваш проект? SBT стандартизирует WebJars, поэтому вы должны использовать WebJars для управления своими зависимостями. К сожалению, мой опыт показал, что этого недостаточно.

Проблема с WebJars

Как уже упоминалось, стандартный способ Scala.js для получения зависимостей JavaScript основан на WebJars. В конце концов, Scala — это язык JVM. Scala.js использует SBT в первую очередь для создания программ, и, наконец, SBT отлично подходит для управления зависимостями JAR.

По этой причине формат WebJar был определен именно для импорта зависимостей JavaScript в мире JVM. WebJar — это файл JAR, включающий веб-активы, где простой файл JAR включает только скомпилированные классы Java. Итак, идея Scala.js заключается в том, что вы должны импортировать свои зависимости JavaScript, просто добавляя зависимости WebJar, подобно тому, как Scala добавляет зависимости JAR.

Хорошая идея, но она не работает

Самая большая проблема с Webjars заключается в том, что произвольная версия случайной библиотеки JavaScript редко доступна в виде WebJar. В то же время подавляющее большинство библиотек JavaScript доступно в виде модулей NPM. Однако существует предполагаемый мост между NPM и WebJars с помощью автоматизированного упаковщика npm-to-webjar . Поэтому я попытался импортировать библиотеку, доступную в виде модуля NPM. В моем случае это была VoxelJS, библиотека для создания миров, подобных Minecraft, на веб-странице. Я попытался запросить библиотеку как WebJar, но мост не удался просто потому, что в дескрипторе нет полей лицензии.

Библиотека WebJar не работает

Вы также можете столкнуться с этим разочаровывающим опытом по другим причинам с другими библиотеками. Проще говоря, похоже, что вы не можете получить доступ к каждой библиотеке в дикой природе как к WebJar. Требование использовать WebJars для доступа к библиотекам JavaScript кажется слишком ограничивающим.

Введите NPM и просмотрите

Как я уже указывал, стандартным форматом упаковки для большинства библиотек JavaScript является Node Package Manager или NPM, включенный в любую версию Node.js. Используя NPM, вы можете легко получить доступ практически ко всем доступным библиотекам JavaScript.

Обратите внимание, что NPM — это менеджер пакетов Node . Node — это серверная реализация движка JavaScript V8, которая устанавливает пакеты, которые Node.js будет использовать на стороне сервера. Как есть, NPM бесполезен для браузера. Тем не менее, полезность NPM была расширена на некоторое время для работы с браузерными приложениями благодаря вездесущему инструменту Browserify, который, конечно же, также распространяется в виде пакета NPM.

Browserify — это, проще говоря, упаковщик для браузера. Он будет собирать модули NPM, создавая «комплект», который можно использовать в браузерном приложении. Многие разработчики JavaScript работают таким образом — они управляют пакетами с помощью NPM, а затем просматривают их для использования в веб-приложении. Имейте в виду, что есть и другие инструменты, которые работают так же, как Webpack.

Заполнение пробела от SBT до NPM

По причинам, которые я только что описал, мне нужен был способ установить зависимости из Интернета с помощью NPM, вызвать Browserify для сбора зависимостей для браузера, а затем использовать их со Scala.js. Задача оказалась немного сложнее, чем я ожидал, но все же выполнимой. Действительно, я сделал работу, и я описываю ее здесь.

Для простоты я выбрал Browserify еще и потому, что обнаружил, что его можно запустить в SBT. Я не пробовал с Webpack, хотя думаю, что это тоже возможно. К счастью, мне не пришлось начинать в вакууме. Уже есть ряд деталей:

  • SBT уже поддерживает NPM. sbt-web , разработанный для Play Framework, может устанавливать зависимости NPM.
  • SBT поддерживает выполнение JavaScript. Вы можете запускать инструменты Node, не устанавливая сам Node, благодаря sbt-jsengine .
  • Scala.js может использовать сгенерированный пакет. В Scala.js есть функция конкатенации для включения в ваше приложение произвольных библиотек JavaScript.

Используя эти функции, я создал задачу SBT, которая может загружать зависимости NPM, а затем вызывать Browserify, создавая файл bundle.js . Я попытался интегрировать процедуру в цепочку компиляции, и я могу запускать все это автоматически, но необходимость обрабатывать объединение при каждой компиляции просто слишком медленная. Кроме того, вы не меняете зависимости все время; следовательно, разумно, что вам нужно время от времени создавать пакет вручную при изменении зависимостей.

Итак, мое решение состояло в том, чтобы построить подпроект. Этот подпроект загружает и упаковывает библиотеки JavaScript с помощью NPM и Browserify. Затем я добавил команду bundle для сбора зависимостей. Полученный пакет добавляется к ресурсам, которые будут использоваться в приложении Scala.js.

Предполагается, что вы должны выполнять этот «пакет» вручную всякий раз, когда вы меняете свои зависимости JavaScript. Как уже упоминалось, это не автоматизировано в цепочке компиляции.

Как использовать упаковщик

Если вы хотите использовать мой пример, сделайте следующее: сначала извлеките репозиторий с помощью обычной команды Git.

 git clone https://github.com/sciabarra/scalajs-browserify/

Затем скопируйте папку bundle в свой проект Scala.js. Это подпроект для комплектации. Для подключения к основному проекту необходимо добавить в файл build.sbt следующие строки:

 val bundle = project.in(file("bundle")) jsDependencies += ProvidedJS / "bundle.js" addCommandAlias("bundle", "bundle/bundle")

Кроме того, вы должны добавить следующие строки в ваш файл project/plugins.sbt :

 addSbtPlugin("com.typesafe.sbt" % "sbt-web" % "1.1.1") addSbtPlugin("com.typesafe.sbt" % "sbt-js-engine" % "1.1.3")

После этого у вас будет новая команда bundle , которую вы можете использовать для сбора зависимостей. Он создаст файл bundle.js в вашей папке src/main/resources .

Как пакет включается в ваше приложение Scala.js?

Только что описанная команда bundle собирает зависимости с помощью NPM, а затем создает bundle.js . Когда вы запускаете команды fastOptJS или fullOptJS , ScalaJS создаст myproject-jsdeps.js , включая все ресурсы, указанные вами как зависимость JavaScript, а значит, и ваш bundle.js . Чтобы включить связанные зависимости в ваше приложение, вы должны использовать следующие включения:

 <script src="target/scala-2.11/myproject-jsdeps.js"></script> <script src="target/scala-2.11/myproject-fastopt.js"></script> <script src="target/scala-2.11/myproject-launcher.js"></script>

Теперь ваш пакет доступен как часть myproject-jsdeps.js . Бандл готов, и мы немного доделали нашу задачу (импорт зависимостей и их экспорт в браузер). Следующим шагом является использование библиотек JavaScript, что является другой проблемой, проблемой кодирования Scala.js. Для полноты картины мы сейчас обсудим, как использовать пакет в Scala.js и создавать фасады для использования импортированных нами библиотек.

С помощью Scala.js вы можете обмениваться кодом между сервером и клиентом.

С помощью Scala.js вы можете легко обмениваться кодом между сервером и клиентом.
Твитнуть

Использование универсальной библиотеки JavaScript в вашем приложении Scala.js

Напомним, что мы только что увидели, как использовать NPM и Browserify для создания пакета и включения этого пакета в Scala.js. Но как мы можем использовать общую библиотеку JavaScript?

Полный процесс, который мы подробно объясним в оставшейся части поста, таков:

  • Выберите свои библиотеки из NPM и включите их в bundle/package.json .
  • Загрузите их с помощью require в файл модуля библиотеки в bundle/lib.js .
  • Напишите фасады Scala.js для интерпретации объекта Bundle в Scala.js.
  • Наконец, закодируйте свое приложение, используя недавно типизированные библиотеки.

Добавление зависимости

Используя NPM, вы должны включить свои зависимости в файл package.json , который является стандартным.

Итак, предположим, вы хотите использовать две известные библиотеки, такие как jQuery и Loadash. Этот пример предназначен только для демонстрации, потому что уже существует отличная оболочка для jQuery, доступная как зависимость для Scala.js с соответствующим модулем, а Lodash бесполезен в мире Scala. Тем не менее, я думаю, что это хороший пример, но берите его только как пример.

Итак, перейдите на веб-сайт npmjs.com и найдите библиотеку, которую хотите использовать, а также выберите версию. Предположим, вы выбрали jquery-browserify версии 13.0.0 и lodash версии 4.3.0. Затем обновите блок dependencies вашего packages.json следующим образом:

 "dependencies": { "browserify": "13.0.0", "jquery-browserify": "1.8.1", "lodash": "4.3.0" }

Всегда сохраняйте browserify , так как это необходимо для создания пакета. Однако вы не обязаны включать его в комплект.

Обратите внимание, что если у вас установлен NPM, вы можете просто ввести из каталога bundle :

 npm install --save jquery-browserify lodash

Он также обновит package.json . Если у вас не установлен NPM, не беспокойтесь. SBT установит Java-версию Node.js и NPM, загрузит необходимые JAR-файлы и запустит их. Все это управляется, когда вы запускаете команду bundle из SBT.

Экспорт библиотеки

Теперь мы знаем, как загружать пакеты. Следующим шагом является указание Browserify собрать их в пакет и сделать их доступными для остальной части приложения.

Browserify — это сборщик require , эмулирующий поведение Node.js для браузера, что означает, что вам нужно где-то иметь require импорта вашей библиотеки. Поскольку нам нужно экспортировать эти библиотеки в Scala.js, пакет также генерирует объект JavaScript верхнего уровня с именем Bundle . Итак, что вам нужно сделать, так это отредактировать lib.js , который экспортирует объект JavaScript, и потребовать, чтобы все ваши библиотеки были полями этого объекта.

Если мы хотим экспортировать в Scala.js библиотеки jQuery и Lodash, в коде это означает:

 module.exports = { "jquery": require("jquery-browserify"), "lodash": require("lodash") }

Теперь просто выполните bundle команд, и библиотека будет загружена, собрана и помещена в пакет, готовый к использованию в вашем приложении Scala.js.

Доступ к пакету

Слишком далеко:

  • Вы установили подпроект пакета в свой проект Scala.js и правильно его настроили.
  • Для любой библиотеки, которую вы хотели, вы добавили ее в package.json .
  • Вы требовали их в lib.js
  • Вы выполнили команду bundle .

В результате у вас теперь есть объект JavaScript верхнего уровня Bundle , предоставляющий все точки входа для библиотек, доступных в виде полей этого объекта.

Теперь вы готовы использовать его со Scala.js. В простейшем случае вы можете сделать что-то вроде этого для доступа к библиотекам:

 @js.native object Bundle extends js.Object { def jquery : js.Any = js.native def lodash: js.Any = js.native }

Этот код позволяет получить доступ к библиотекам из Scala.js. Однако это не так, как вы должны делать со Scala.js, потому что библиотеки все еще нетипизированы. Вместо этого вы должны написать типизированные «фасады» или обертки, чтобы вы могли использовать изначально нетипизированные библиотеки JavaScript масштабированным типизированным способом.

Я не могу рассказать вам, как писать фасады, так как это зависит от конкретной библиотеки JavaScript, которую вы хотите обернуть в Scala.js. Я покажу только пример, чтобы завершить обсуждение. Пожалуйста, ознакомьтесь с официальной документацией Scala.js для получения более подробной информации. Кроме того, вы можете ознакомиться со списком доступных фасадов и прочитать исходный код для вдохновения.

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

Обертывание JavaScript API в Scala.js

Когда вы используете require в JavaScript, вы получаете объект, который может быть самым разным. Это может быть функция или объект. Это может быть даже просто строка или логическое значение, и в этом случае require вызывается только для побочных эффектов.

В моем примере jquery — это функция, возвращающая объект, предоставляющий дополнительные методы. Типичное использование — jquery(<selector>).<method> . Это упрощение, потому что jquery также допускает использование $.<method> , но в этом упрощенном примере я не буду рассматривать все эти случаи. Обратите внимание, что для сложных библиотек JavaScript не все API можно легко сопоставить со статическими типами Scala. Возможно, вам придется прибегнуть к js.Dynamic , предоставляющему динамический (нетипизированный) интерфейс для объекта JavaScript.

Итак, чтобы зафиксировать наиболее распространенный вариант использования, я определил в объекте Bundle jquery :

 def jquery : js.Function1[js.Any, Jquery] = js.native

Эта функция вернет объект jQuery. Экземпляр трейта в моем случае определяется одним методом (упрощение, вы можете добавить свой собственный):

 @js.native trait Jquery extends js.Object { def text(arg: js.Any): Jquery = js.native }

Для библиотеки Lodash мы моделируем всю библиотеку как объект JavaScript, поскольку это набор функций, которые вы можете вызывать напрямую:

 def lodash: Lodash = js.native

Где черта lodash выглядит следующим образом (также упрощение, вы можете добавить свои методы здесь):

 @js.native trait Lodash extends js.Object { def camelCase(arg: js.Any): String = js.native }

Используя эти определения, теперь мы можем, наконец, написать код Scala, используя базовые библиотеки jQuery и Lodash , которые загружаются из NPM, а затем отображаются в браузере:

 object Main extends JSApp { def main(): Unit = { import Bundle._ jquery("#title").text(lodash.camelCase("This is a test")) } }

Вы можете проверить полный пример здесь.

Заключение

Я разработчик Scala, и я был взволнован, когда обнаружил Scala.js, потому что я мог использовать один и тот же язык как для сервера, так и для клиента. Поскольку Scala больше похожа на JavaScript, чем на Java, Scala.js довольно легко и естественно работает в браузере. Кроме того, вы также можете использовать мощные функции Scala в виде богатой коллекции библиотек, макросов, мощных IDE и инструментов сборки. Другие ключевые преимущества заключаются в том, что вы можете совместно использовать код между сервером и клиентом. Есть много случаев, когда эта функция полезна. Если вы используете транспилятор для Javascript, такой как Coffeescript, Babel или Typescript, вы не заметите слишком много различий при использовании Scala.js, но все же есть много преимуществ. Секрет в том, чтобы взять лучшее из каждого мира и убедиться, что они хорошо работают вместе.

Связанный: Зачем мне изучать Scala?