Blog
Play Framework with WebJars で管理画面をサクッと作ってみる
こんにちは!DynalystというDSPプロダクトでエンジニアをやっている瓜生と申します。
はじめに
Dynalystの管理画面は Play Framework + AdminLTE で作られています。
AdminLTE は、select2 や datetimepicker と言った JavaScript の plugin も含まれる巨大なCSSフレームワークです。
それらの plugin を使う時に、わざわざ AdminLTE のソースをダウンロードして、
JavaScript や CSS を Play Framework のプロジェクトにある public ディレクトリ以下にコピーして使っているのですが、
これがなかなかにツラい…
なんかいい方法ないもんかと模索している時に、今回の WebJars を今更ながら見つけたので、導入手順と共に紹介したいと思います。
WebJarsってなに?
WebJars はリンク先の紹介文にもある通り、クライアントサイドのライブラリたちをJARにした Java ライブラリです。
Java のライブラリですので、Grails や Spring Boot でも利用できるようです!
導入
WebJars の紹介はココらへんにして、早速導入していきます!
SBT
はじめに SBT の設定です。
WebJars 本体、jQuery、AdminLTE と AdminLTE で必要になるパッケージを入れてみましょう!
Play Framework のプロジェクトで
1 2 3 4 5 6 |
libraryDependencies ++= Seq( "org.webjars" %% "webjars-play" % "2.5.0", "org.webjars.bower" % "adminlte" % "2.3.0", "org.webjars" % "font-awesome" % "4.6.3", "org.webjars" % "jquery" % "2.1.4" ) |
これを追加するだけです。
アプリケーション
次にアプリケーション側の修正です。
まずは、conf/routes に下記を追加します。
1 |
GET /webjars/*file controllers.WebJarAssets.at(file) |
このように宣言することで、SBT で設定された AdminLTE 等の JAR に存在するファイルにアクセスすることができます。
次に、Controller を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import javax.inject.Inject import controllers.WebJarAssets import play.api.mvc.{Action, Controller} import views.html import scala.concurrent.Future class WebJarsController @Inject() (webJarAssets: WebJarAssets) extends Controller { def index() = Action.async { implicit request => Future.successful(Ok(html.webjars(webJarAssets))) } } |
View は長いですがこんな感じのものを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
@(webJarAssets: WebJarAssets)() <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" media="screen" href='@routes.WebJarAssets.at(webJarAssets.locate("adminlte/2.3.0/bootstrap/css/bootstrap.min.css"))'> <link rel="stylesheet" media="screen" href='@routes.WebJarAssets.at(webJarAssets.locate("adminlte/2.3.0/dist/css/skins/_all-skins.min.css"))'> <link rel="stylesheet" media="screen" href='@routes.WebJarAssets.at(webJarAssets.locate("adminlte/2.3.0/dist/css/AdminLTE.min.css"))'> <script src='@routes.WebJarAssets.at(webJarAssets.locate("jquery/2.1.4/jquery.min.js"))' type="text/javascript"></script> </head> <body class="skin-blue sidebar-mini"> <div class="wrapper"> <header class="main-header"> <a href="#" class="logo"> <span class="logo-mini"><b>Dy</b></span> <span class="logo-lg"><b>Dynalyst</b></span> </a> <nav class="navbar navbar-static-top"> <a href="#" class="sidebar-toggle" data-toggle="offcanvas" role="button"> <span class="sr-only">Toggle navigation</span> </a> </nav> </header> <aside class="main-sidebar"> <section class="sidebar" style="height: auto;"> <ul class="sidebar-menu"> <li class="treeview active"> <a href="#"> <i class="fa fa-users"></i> <span>Test</span> <span class="pull-right-container"><i class="fa fa-angle-left pull-right"></i></span> </a> <ul class="treeview-menu"> <li class="active"> <a href="#"><i class="fa fa-circle-o"></i> Test1</a> </li> <li> <a href="#"><i class="fa fa-circle-o"></i> Test2</a> </li> <li> <a href="#"><i class="fa fa-circle-o"></i> Test3</a> </li> </ul> </li> </ul> </section> </aside> </div> <div class="content-wrapper"> <section class="content"> <div class="row"> <p>Test</p> </div> </section> </div> <footer class="main-footer"> <strong>Copyright © 2016 Dynalyst</strong> All rights reserved. </footer> </body> </html> |
アプリケーション側の修正は以上です!
これだけでこのような画面を作ることができます。
バンザイ WebJars!バンザイ AdminLTE!
後は使いたいパッケージをこちらから検索し、libraryDependencies に追加すれば、同じ要領で利用可能です。
ハマった点
1. webJarAssets.locate の文字列って何渡せばいいの?
WebJars のドキュメントなどには、こう書けば使えるよという感じでしれっと書いてあるのですが、
実際やってみると、あれ?何渡せばいいの?って感じで戸惑いました。
WebJars のページで検索したパッケージの横にある「xxx Files」というリンクをクリックすると、
そのパッケージ内にあるファイルのパス一覧がポップアップで表示されます。
その一覧に表示される使いたいファイルパスの「META-INF/resources/webjars/」以下の文字列を渡せば良いようです。
2. 追加したパッケージのファイルが読めない!
WebJarsのパッケージを追加する時の注意点として、Play Framework を起動したままパッケージを追加すると、下記のようなエラー画面が表示されてしまいます。
起動したままなので、追加したパッケージをロードされていないためにファイルが見つからないよ!と言われるわけですね。
なので、この時は Play Framework を再起動すれば、追加したパッケージを使えるようになります。
SBTで管理されているため、ファイルが無い時にエラーになってくれるのもイイですね!
WebJarsの実装
最後に、 WebJars 内の実装簡単に説明します。
利用している at, locate の method を下記に抜粋します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@Singleton class WebJarAssets @Inject() (errorHandler: HttpErrorHandler, configuration: Configuration, environment: Environment) extends AssetsBuilder(errorHandler) { val WebjarFilterExprDefault = """.*""" val WebjarFilterExprProp = "org.webjars.play.webJarFilterExpr" lazy val webJarFilterExpr = configuration.getString(WebjarFilterExprProp).getOrElse(WebjarFilterExprDefault) val webJarAssetLocator = new WebJarAssetLocator( WebJarAssetLocator.getFullPathIndex( new Regex(webJarFilterExpr).pattern, environment.classLoader)) def at(file: String): Action[AnyContent] = { this.at("/" + WebJarAssetLocator.WEBJARS_PATH_PREFIX, file) } def locate(file: String): String = { webJarAssetLocator.getFullPath(file).stripPrefix(WebJarAssetLocator.WEBJARS_PATH_PREFIX + "/") } } public class WebJarAssetLocator { public static final String WEBJARS_PATH_PREFIX = "META-INF/resources/webjars"; } |
- locate → SBTで設定したパッケージのJARファイルから「META-INF/resources/webjars」を除いたパスをフルパスで返してくれる。
- at → Play Framework の at を利用し、「META-INF/resources/webjars」で始まる引数の file の Contentsを返す。
となっていて、シンプルな実装になっているなーと感じました。
パフォーマンスってどうなるの?
http://hipfinger.net/posts/nginx_play_frontend_proxy/
こちらに nginx + Playframework のベンチマークがありますが、キャッシュがなくてもパフォーマンスにそこまで遜色ないことが分かります。
WebJars も Playframework の Assets のラッパーのようなものなので、WebJars を使ってもパフォーマンスが劣化することはなさそうです。
最後に
WebJars には様々なフロントエンド用のライブラリがこれでもかというほどありますし、SBT でバージョン管理もできるので、
これは是非使うべき代物だということが分かりました!
WebJars のドキュメントはあるものの、AdminLTE などのパッケージを利用した情報はあまりないようですので、
一例として参考にしていただけたらと思います。
Author