WebJars + RequireJS

    День добрый, читатели Хабра!
    В этой статье спешу вам рассказать (хоть и с опозданием) о том, что такое WebJars на примере приложения в Play Framework.

    image В практически любом веб-приложении нельзя обойтись без сторонних javascript-библиотек. Самый простой способ добавить их: скачать и добавить в проект, а также добавить файл в git-репозиторий. Решение годное, но лично для меня наличие в проекте какой-либо статики малость раздражает. Есть другой метод: указывать ссылку на внешний хостинг js-библиотек такой как google, yandex. В принципе вариант, но в моей практике были случаи, когда необходимо было продолжить разработку а доступ в интернет оставлял желать лучшего либо его вообще не было, в итоге клиентская часть не функционировала. Наиболее годным решением мне видится добавления js библиотеки в качестве зависимости в проект, с подобным подходом вы могли сталкивать в Ruby on Rails.

    Ближе к делу

    У нас есть веб-приложение, написанное на java-based языке и нам нужно добавить в него пару javascript библиотек, именно для такого случая нам идеально подойдут WebJars.

    WebJars — набор библиотек, каждая из которых содержит в себе JS библиотеку и/или CSS модули.

    Полный список библиотек можно посмотреть здесь. У каждой из них есть несколько версий, которые соответствуют версии js-библиотеки. Все WebJars доступны на Maven репозитории.

    В данном примере я опишу, как WebJars в приложение Play Framework. Описания добавления WebJars в другие фреймворки можно найти здесь.

    Приступаем

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

    К примеру:

    libraryDependencies ++= Seq(
      cache,
      ws,
      "org.webjars" %% "webjars-play" % "2.3.0-2",
      "org.webjars" % "requirejs" % "2.1.14-3",
      "org.webjars" % "requirejs-domready" % "2.0.1-2",
      "org.webjars" % "jquery" % "2.1.1",
      "org.webjars" % "bootstrap" % "3.2.0-2"
    )
    


    Если вы скомпилируете проект, вы можете обнаружить новую папку web-modules в директории target/web. В ней как раз и содержаться добавленные в проект js и css файлы.

    Обращаться к ним из view можно по старинке @routes.Assets.at(«lib/requirejs/require.js»), но для этого нужно знать полный путь. Другой более кошерный способ использовать готовые контроллеры WebJars. Для этого добавим в conf/routes новый маршрут специально для WebJars:

    GET        /lib/*file              controllers.WebJarAssets.at(file)
    


    А для обращения к ним из view мы можем использовать еще метод от WebJars WebJarAssets.locate. Указанный метод сам ищет необходимый файл в каталоге с WebJars. В итоге получаем:

    <link rel="stylesheet" href="@routes.WebJarAssets.at(WebJarAssets.locate("bootstrap.min.css"))">
    


    Что на html странице будет выглядеть как

    <link rel="stylesheet" href="/lib/bootstrap/3.2.0-2/css/bootstrap.min.css">
    


    Удобно не правда ли? Но это еще не все.

    RequireJS

    Наверняка вы уже слышали о прелестях этого замечательного js-фреймворка, поэтому не буду распинаться на эту тему.

    WebJars содержит в себе механизм для более удобной интеграции с RequireJS. Если вы зайдете в каталог target/web/web-modules/main/webjars/lib/jquery, то обнаружите файл webjars-requirejs.js c содержимым.

    /*global requirejs */
    
    // Ensure any request for this webjar brings in jQuery.
    requirejs.config({
        paths: { "jquery": webjars.path("jquery", "jquery") },
        shim: { "jquery": { "exports": "$" } }
    });
    


    В каждом WebJar есть файл с конфигом для RequireJS. Чтобы получить общий конфиг создадим новый view с именем requireJsConfig.

    @Html(org.webjars.RequireJS.getSetupJavaScript(routes.WebJarAssets.at("").url))
    


    Добавим новый экшн в контроллер Application.

    import play.api.cache.Cached
    import views._
    import play.api.Play.current
    
      def requireJsConfig = Cached("require_js_config") {
        Action {
          Ok(html.requireJsConfig()).as("application/javascript")
        }
      }
    


    Результат выполнения мы кэшируем, поскольку его содержимое будет меняться только при добавлении/удалении используемых в проекте WebJars. Также результат мы помечаем как application/javascript, чтобы явно указать в содержимое является javascript'ом.

    Добавляем новый маршрут:

    GET        /files/config.js        controllers.Application.requireJsConfig
    


    Забавы ради url прописываем как у статического js-файла.

    Проверяем что получилось, в браузере переходим по url http://localhost:9000/files/config.js

    var webjars = {
        versions: {"requirejs-domready":"2.0.1","requirejs":"2.1.14-3","bootstrap":"3.2.0-2","jquery":"2.1.1"},
        path: function(webJarId, path) {
            console.error('The webjars.path() method of getting a WebJar path has been deprecated.  The RequireJS config in the ' + webJarId + ' WebJar may need to be updated.  Please file an issue: http://github.com/webjars/' + webJarId + '/issues/new');
            return ['/lib/' + webJarId + '/' + webjars.versions[webJarId] + '/' + path];
        }
    };
    
    var require = {
        callback: function() {
            // Deprecated WebJars RequireJS plugin loader
            define('webjars', function() {
                return {
                    load: function(name, req, onload, config) {
                        if (name.indexOf('.js') >= 0) {
                            console.warn('Detected a legacy file name (' + name + ') as the thing to load.  Loading via file name is no longer supported so the .js will be dropped in an effort to resolve the module name instead.');
                            name = name.replace('.js', '');
                        }
                        console.error('The webjars plugin loader (e.g. webjars!' + name + ') has been deprecated.  The RequireJS config in the ' + name + ' WebJar may need to be updated.  Please file an issue: http://github.com/webjars/webjars/issues/new');
                        req([name], function() {;
                            onload();
                        });
                    }
                }
            });
    
            // All of the WebJar configs
    
    
    requirejs.config({"paths":{"requirejs-domready":["/lib/requirejs-domready/2.0.1/domReady","domReady"]}})
    requirejs.config({"paths":{}})
    requirejs.config({"paths":{"bootstrap":["/lib/bootstrap/3.2.0-2/js/bootstrap","js/bootstrap"],"bootstrap-css":["/lib/bootstrap/3.2.0-2/css/bootstrap","css/bootstrap"]},"shim":{"bootstrap":["jquery"]}})
    requirejs.config({"paths":{"jquery":["/lib/jquery/2.1.1/jquery","jquery"]},"shim":{"jquery":{"exports":"$"}}})    }
    }
    


    Данный скрипт настраивает RequireJS необходимым нам образом и должен предшествовать самому RequireJS.

    Финал

    Создадим небольшой coffescript файл (простите, если кого-то этим огорчил, но от чистого javascript я подустал) в каталоге app/assets/js/main.coffee.

    require ['jquery','requirejs-domready!'],->
      $('body').text 'Success'
    


    И в интересующей нас view, добавим.

    <script type="application/javascript" src="@routes.Application.requireJsConfig()"></script>
    <script data-main="@routes.Assets.at("js/main.js")" src="@routes.WebJarAssets.at(WebJarAssets.locate("require.min.js"))"></script>
    


    Запускаем и видим, что все необходимые javascript-файлы загрузились и код выполнился.

    Итог

    В данном примере мы познакомились с возможностями WebJars. От себя добавлю, что ничего кардинально-инновационного я не обнаружил, но все же они облегчают frontend разработку.
    Поделиться публикацией

    Комментарии 13

      0
      На любую view будет загружаться полная конфигурация всех WebJar'ов? Т.е. если библиотека используется только на одной какой-то view, мы её всё равно загрузим во всех? Я правильно понял?
        0
        Да, все правильно

        Но можно делать кастомные конфиги

        Вместо

        @Html(org.webjars.RequireJS.getSetupJavaScript(routes.WebJarAssets.at("").url))
        


        пишем

        var require = {
            callback: function() {
              // default requirejs configs
              @for(webJarJson <- org.webjars.RequireJS.getSetupJson(routes.WebJarAssets.at("").url).values()) {
                requirejs.config(@Html(webJarJson.toString));
              }
        }
        


        Как видим в здесь цикл, который перебирает все WebJars, в него можно добавить filter и пропускать ненужные WebJars

          0
          И кстати сразу хочу предупредить при добавлении AngularJS в качестве WebJars, сгенирится весьма внушительный конфиг

          Если это вызывает особое раздражение, то разумеется лучше писать конфиг для RequireJS руками
            +2
            Мне не понятно зачем его вообще генерить? Я на темплейте делаю так

            @Html(org.webjars.play.RequireJS.setup(app))


            Где app это переменная, которая хранит путь до файла модуля. Сам файл например такой

            'use strict';
            
            require(['angular','bootstrap'],
                function(angular) {
                    angular.module('cag', []);
                    angular.bootstrap(document, ['cag']);
                });
            


            И у меня на странице зависимости только те которые нужны.

            И ещё один вопрос. А зачем ReqireJS явно указан в WebJars?
            Насколько я помню RequireJS с определенной версии поставляется уже вместе с Play 2.
              0
              А всё понял. По сути это всё одинаково. Однако у вас всё кэшированно и быстро.
            +5
            По моему слегка изврат.
            Создается впечатление что ребята которые работают только на бекенде вообще не хотят использовать очень хорошо обкатанные методики и подходы которые уже не первый год отлично работают в фронт-енде, вместо этого придумывая собственный велосипед.

            Идея вытягивания js/css зависимостей из мавен репозитория мне решительно не нравиться, так как в случае более-менее сложных задач на фронт-енде будет очень не хорошо зависеть от того есть ли нужный WebJar или нет (список конечно очень большой но некоторых хороших библиотек я там не увидел, а зятягивать часть через WebJar а часть еще как-то — моветон)
            Я бы посмотрел в сторону bower.io/ для решения такой задачи. тут ребята используют git репозитории для получения необходимых артефактов, в бовере храниться только регистр.

            Также очень советую посмотреть на gulpjs.com/ или gruntjs.com/ и всевозможной обработки / минификации / обфускации / ужатия картинок / .
              0
              Я бы сказал это очень изврат.

              Но мне человеку далекому от фронтэнда не хватило бы терпения. Сейчас всяких штук в окончанием JS столько, что глаз коли. Вот если бы кто-нибудь сделал верно правильный template для activator, где было бы показано как эти все штуки подружить, я был бы очень рад. Сейчас я как раз активно работаю на стэке play/angular и да, не хватает многих плюшек. Например мне хотелось бы весь фронтэнд как-то отделить от бэкэнда и каким-либо образом покрыть хотя бы минимально тестами. Но вот так и живем. Кто во что горазд, гром их порази.
                0
                npm
                npm can be used as well as WebJars by declaring a package.json file in the root of your project. Assets from npm packages are extracted into the same lib folder as WebJars so that, from a code perspective, there is no concern whether the asset is sourced from a WebJar or from an npm package.


                Гласит документация Play Framework

                Нужно будет поэкспериментировать в этом плане
                  0
                  Надо как-нибудь будет поделиться наработками.
                0
                и можно еще настроить бридж между gulp/grunt и maven/sbt
                  +1
                  есть примеры?
                    0
                    На github'e думаю их довольно много, примеров.
                    У меня например, на одном проекте используется grunt-maven-plugin, с SBT к сожалению не интегрировал, из-за ненадобности, со всем пока play справляется, но по запросу 'sbt grunt plugin' там целых три штуки, возможно и примеры там есть.
                +1
                >> Самый простой способ добавить их: скачать и добавить в проект, а также добавить файл в git-репозиторий.
                bower не пробовали?

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

                Самое читаемое