Сразу хочу оговориться, что данная статья не предназначена для тех, у кого разработка frontend основная деятельность. Целевая аудитория: разработчики backend которым понадобилось срочно прикрутить web UI или просто интересуются новыми областями, ну и возможно fullstack разработчики.

Итак, перейдем к проблеме. Вспоминается статья, а также, на просторах Хабра было еще несколько подобных. Представлены они все как шуточные, но как говорится «в каждой шутке есть доля правды», а тут даже и не доля… Но вот в чем вопрос, а действительно ли нужно все это?

Вопрос который я хотел бы поднять, преимущественно о сборке. Именно на этом этапе js-сообщество предлагает неимоверное количество инструментов, необходимость которых совсем не очевидна. Например, сообществом предлагается различные варианты работы с модулями, что имело смысл в свое время, но на данный момент спецификация на import/export поддерживается всеми современными браузерами, и даже недобраузером edge. В силу, оговорённой в начале, задачи, необходимости поддерживать что-либо более старое у нас и нету, так что мы смело останавливаемся на спецификации. А вот то что действительно будет нам необходимо, так это какая либо работа с зависимостями.

Сделаю небольшое отступление, чтобы объяснить выбор технологий, которые будут использоваться в примере для самого frontend’а. Чтобы не копаться в бесчисленном многообразие фреймворков, пойдем от замечательного стандарта. Итак, все становится достаточно просто, виртуального DOM'а там нет, вебкомпоненты есть. На этом можно было бы закончить, но еще js. Если мы решим отделаться чистым js – то придется как-то решать инфраструктурные задачи, избавляться от бойлерплет кода и т.д. Иными словами – городить свой велосипед… Чтобы этого не делать, хотелось бы взять что-нибудь, где все уже сделано и достаточно качественно. Соответственно выбор мой пал на Polymer – фреймворк от Google, которые и пропихнули вебкомпоненты в стандарт.

Чтож, вернемся к сборке. Многие из вас (во всяком случае Java разработчики) привыкли к таким инструментом как maven и gradle, и наверняка успели выяснить, что они прекрасно справляются и с манипуляциями над ресурсами (у maven это конечно немного хуже, но вот у gradle — проблем нет собрать вообще все что угодно). Сообщество же frontend нам предлагает брать npm, для которого нужна nodeJs, но этого не хватит, еще и webpack сверху. А в них еще придется разбираться и ставить каждому разработчику. Эм… Хотелось бы просто выкачать репозиторий, сделать gradlew build и начать работать, так что давайте уж остановимся на привычном gradle.

Совсем без npm мы конечно обойтись не сможем, как минимум, нам нужен репозиторий откуда брать js библиотеки и npm это предоставляет, а вот утилита не обязательна. Можно найти несколько хороших решений вроде этого, но они всегда качают nodeJs, и запускают npm таски. Вариант, но мне бы хотелось бы минимизоровать общение с npm до взаимодействия с репозиторием и только. Как решение было написать свой плагин для gradle. Хоть и писать плагины для gradle достаточно просто, в силу широкого разнообразия возможностей указания версий для npm репозитория, задача несколько усложняется. Благо на это есть понятная документация.

Итак, напишем gradle скрипт для загрузки зависимостей. Используется kotlin dsl по той причине, что переписать на groovy dsl достаточно тривиально, а вот наоборот пришлось бы потратить время, если не было ранее опыта.

repositories {
    mavenLocal()
    jcenter()
}

dependencies {
    classpath("com.github.artfable.gradle:gradle-npm-repository-plugin:0.0.3")
}

apply(plugin = "artfable.npm")

configure<GradleNpmRepositoryExtension> {
    output = "$projectDir/src/libs/"
    dependencies = mapOf(
            Pair("@polymer/polymer", "3.0.5"),
            Pair("@polymer/app-layout", "3.0.1"),
            Pair("@polymer/paper-toolbar", "3.0.1"),
            Pair("@polymer/paper-icon-button", "3.0.1"),
            Pair("@polymer/iron-icons", "3.0.1"),
            Pair("@webcomponents/webcomponentsjs", "2.1.3")
    )
}

Данный плагин добавит нам таск npmLoad. Чтобы позволить IDE красиво сгрупировать, определим его в группу

tasks["npmLoad"].group = "frontend"

Отлично, можно попробовать написать код для нашего frontend. Встроенный сервер Intellij по умолчанию скажет вам что Method Not Allowed на попытку подключить скрипты через import. Чтобы это избежать – поставьте чекбокс allow using requests. (в последних версиях работает и без этого).

Settings (pic. 1)

Попробуем теперь запустить, и… Все равно ничего не заработало. Отвалились зависимости внутри компонентов полимера. Дело в том, что многие js библиотеки пушут импорты с расчетом на какой-нибудь транспилер вроде babel. Такой import выглядит например так:

import '@polymer/polymer/polymer-legacy.js';

Что не правильно, так как для браузера путь должен начинаться только с ‘/’, ‘./’ или ‘../’. Кроме того, в таком виде его нельзя брать ни как относительный, ни как абсолютный, так как здесь идет расчет на начало из корня директории с библиотеками. Следовательно такие пути надо исправить, ну и чтобы не делать это самим – можно взять заготовленный плагин.

В зависимости

classpath("com.github.artfable.gradle:gradle-js-import-fix-plugin:0.0.1")

apply(plugin = "artfable.js.import.fix")

configure<GradleJsImportFixExtension> {
    directory = "$projectDir/src/libs"
}

Добавится task jsImportFix который приведет все импорты в порядок.

Вот так просто, и без необходимости разбираться с горой новый инструментов, можно собрать фронт. Но давайте еще рассмотрим вопрос со стилями. Откровенно говоря, веб-компоненты позволяют избавится от бойлерплейта, а переменные в css, которые уже в стандарте – открывают множество возможностей, так что необходимости в таких вещах как sass уже больше нет. Но вдруг, например вам очень приглянулся bootstrap и захотелось сделать дизайн на его основе.

Найти какой-нибудь плагин для сборки на этот счет не проблема, в основном, все они за основу берут libsass и есть обертка на java — jsass. Единственная проблема у всех (во всяком случае на момент когда я рассматривал их) в дуплицирование зависимых файлов.

Пример:

Файл a:

@import “b”;
@import “c”;

Файлы _b и _c:

@import “d”;

В результате мы получим в основном файле 2 одинаковых блока из файла _c. Можно достаточно просто исправить, если дописать импортер к jsass, api позволяет. Собственно поэтому я опять со своим решением и здесь (если подобных требований нет, то лучше воспользоваться другим решением).

Добавим в список npm зависимостей

Pair("bootstrap", "4.1.3")

Зависимость на плагин

classpath("com.github.artfable.gradle:gradle-sass-plugin:0.0.1")

apply(plugin = "artfable.sass")

configure<GradleLibsassPluginExtension> {
    group(delegateClosureOf<GradleLibsassPluginGroup> {
        sourceDir = "src/sass"
        outputDir = "src/css"
    } as Closure<Any>)
}

tasks.create("processResources")

Фейковый таск processResources придется создать, если не подключён java-plugin (а он нам здесь и не нужен). Это конечно недоработка, потом обязательно исправлю.

Обратите внимание на то, что полученный css файл нужно подключать не в head, так как внутри компонента он тогда не будет виден, а непосредственно в шаблон самого компонента.

Последним шагом можно немного изменить скрипт, чтобы получить привычную директорию build, с готовым проектом что можно деплоить на прод.

Полный код выложил на github (наверно придется потом мигрировать на gitlab...).

Основная идея надеюсь понятна, а дальше можно добавлять достаточно просто абсолютно все что угодно.

Комментарии (15)


  1. pesh1983
    02.02.2019 23:01
    +5

    Вот так просто, и без необходимости разбираться с горой новый инструментов, можно собрать фронт.

    Ну как минимум с gradle и kotlin/groovy разобраться придется. Бэк не джавой единой живёт, не все бэк разработчики пишут на ней и не все бэки реализованы на джаве. Я бы озаглавил статью вроде "Frontend для Java Backend девелопера", так будет честнее что-ли)


    1. artfable Автор
      03.02.2019 00:51
      -1

      В целом согласен, но вы делать именно Java не хотелось бы. Gradle, например, также используют кто под Unity пишет, может еще кто.


      1. TimsTims
        03.02.2019 16:12

        Unity же не относится к бэкенду. Путаницы не будет.


  1. Serge78rus
    02.02.2019 23:38

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


  1. justboris
    03.02.2019 02:18
    +2

    Какие у вас планы на поддержку и развитие проекта? До полноценной замены npm ему еще очень далеко:


    • Логика работы с транзитивными зависимостями не реализована. Npm умеет обрабатывать ситуации, когда модули зависят от одного пакета, но разных версий. У вас сейчас модули складываются в плоский список, но иногда они могут быть и деревом.
    • В import-fix плагине код обрабатывается регулярками. Это очень ненадежно, и может сломаться, например, если где то будет import c двойными кавычками: import "@polymer/polymer". Для надежной работы нужен парсер и работа с AST.
    • Ну и для продакшена скрипты лучше все-таки помодульно не грузить, а склеить в пачку, чтобы пользователям трафик экономить. Какие у вас есть решения для этой ситуации?


    1. Gugic
      03.02.2019 02:41

      Помимо склейки есть еще минификация и оптимизация при компиляции (см. например closure compiler), что также весьма полезно.


    1. artfable Автор
      03.02.2019 16:25

      Загрузка транзитивных зависимостей есть. Что касается ситуаций зависимостями от одного модуля: плагин ищет саму последнюю версию которая будет удовлетворять все пакеты что требуют. Если же такое невозможно и происходит коныликт, то есть возможность указать excludes и обозначить версию, которы вы хотите видеть. (в README к плагину все описанно).

      Учитывая HTML/2 и кеширование — как раз лучше для прода грузить помодульно. Но если хочется все равно склеить — можно добавить какой-нибудь плагин, например есть плагин для google closure compiler, которые еще и минификацию может сделать.


      1. justboris
        03.02.2019 17:28

        Учитывая HTML/2 и кеширование — как раз лучше для прода грузить помодульно

        Несмотря на кучу презентаций, для своих собственных сайтов Google ресурсы все-таки бандлит. Кроме того, есть вот такое мнение. Еще вот здесь есть объяснение от разработчика Nginx, что не так с HTTP/2. Так что, склейка ресурсов все еще нужна.


        Closure compiler занимается минификацией отдельных файлов. Перед этим нужно еще разрезолвить импорты и собрать их всех вместе. Что для этого посоветуете?


        1. artfable Автор
          03.02.2019 17:53

          Вот например плагин который использует closure compiler, умеет собирать все импорты вместе и еще много чего.


          1. justboris
            03.02.2019 18:18

            Насколько я вижу из реализации combineJs task, она просто позволяет найти и склеить все файлы по списку. А как сделать так, чтобы я указал входной файл src/index.js, а система сборки сама проанализировала импорты и добавила @polymer/polymer и @polymer/app-layout, которые я использую, например?


            1. artfable Автор
              03.02.2019 18:35

              Ну тут несколько вариантов: можно например дописать и сделать пул реквест, можно найти что-либо другое, ну и на крайний случай, взять npm плагин — если это кажется действительно проще.

              У меня не было цели найти silver bullet, которая бы закрыла потребности абсолютно всех. Цель была показать идею, в каком направление смотреть. И разобрал я те случаи, которые считаю наиболее необходимыми, а как уже заметил выше — сборка в один, таковой для меня не является.


              1. justboris
                03.02.2019 19:17

                Понятно, то есть этот плагин для случая «хочу взять пару скриптов и npm и все».

                Для написания более-менее толстых SPA все-таки добро пожаловать в мир gradle-node-plugin.

                Интересно, а насколько большой (по числу строк Javascript) у ваc проект с использованием polymer и gradle-npm-repository-plugin?


  1. RiseOfCat
    03.02.2019 16:05

    Сделал pull request в твой репозиторий:
    github.com/artfable/frontend-demo/pull/1

    Но почему то у меня не получается запустить проект.
    Браузер ругается:
    Failed to resolve module specifier "@polymer/polymer/polymer-legacy.js". Relative references must start with either "/", "./", or "../".


    1. artfable Автор
      03.02.2019 16:08

      Вы добавили runLocalServer таск, который берет src директорию, а не то что в build. js-import-fix плагин нацелен же на build — соответственно пути и не исправленны. С отсутствием libs директории вчера уже поправил.


      1. RiseOfCat
        03.02.2019 17:50

        Спасибо!