MiddlemanでVisualforceを書く

  • 3
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

MiddlemanとはRuby製の静的サイトジェネレータで、普段Webアプリを書いている時に使っているテンプレートエンジン(Slim, HAML, ERBなど、NodeならJadeとか)やCoffeeScriptなどのAltJs、Sassなどのプリプロセッサを使って静的サイトを作っちゃおうというツールです。競合ツールはたくさんあって、NodeならGruntでがんばったり、最近だとWebPackが人気だと思います。

最近はapex:タグを投げ捨てても許される風潮になってきたので、それならSalesforce1アプリじゃなくてもdoctype=html-5.0で書いちゃおう、それならHTMLやJavaScriptを直接書きたくないから静的サイトジェネレータ使おう、と思って始めました。

まあatskimuraさんのエントリの「Force.comへのデプロイ」のところとだいたい同じなんですが!

TLDR

何ができるようになるか

Slim, Sass, CoffeeScriptでVisualforceと性的リソース作れるよ!コマンド一発でデプロイできるようになるよ!(デプロイはAnt Migration Tool)

必要なもの

RubyとNode.js(Bower使ってるので)があれば十分ですが、Salesforce Migration Toolもあったほうがいいと思います。

gem install bundlernpm install -g bowerも必要です。

サンプルと使い方

こちらのリポジトリからcloneして、bundle installbower installを実行すれば使えるようになります。Middlemanのサイトにはmiddleman initしろと書いてますが、init済みのプロジェクトをpushしてるので必要ないです。

サンプルはすぐbundle exec middleman buildでビルドできるはずです。buildディレクトリにVisualforceと静的リソース、package.xmlが出力されるので、Migration Toolでデプロイできます。また、Middlemanにも開発サーバ機能があって、bundle exec middlemanで起動します。起動したら http://localhost:4567/__middleman でサイトマップを確認できます。

build.xmlにはdeployUnpackagedとdryRun(CheckOnly)を定義しています。build.propertiesは入ってないのでサンプルからコピーしてください。

こういうのが簡単に作れます

サンプルに入っているVisualforceをSitesで公開しています。
https://tarot-developer-edition.na14.force.com/myfirstsite/sample

RemoteActionはCRUC/FLS制御していないので使えますが、RemoteObjectの方は参照権限を外しているのでエラーになります。JavaScriptコンソールでご確認ください。

サンプルの解説

Visualforce開発で触るのは主に↓のディレクトリです。

ローカルディレクトリ 開発サーバ ビルド後
/source/pages/*.page.slim /pages/*.page と
/pages/*.page-meta.xml
/build/pages/*.page と
/build/pages/*.page-meta.xml
/source/pages/layouts/*.slim なし なし
/source/staticresouces/css/*
/source/staticresouces/js/*
/source/staticresouces/img/*
/staticresources/css/*
/staticresources/js/*
/staticresources/img/*
/build/staticresources/assets.resouce
にまとめてZip圧縮される
/bower_component
の配下のフォント
/staticresources/fonts/* /build/staticresources/assets.resouce
これも同じZipにまとめられる
/source/staticresouces/sample/* /staticresources/sample/* /build/staticresources/sample.resouce
ディレクトリ配下をZipにまとめる
/source/package.xml 見ても意味が無い /build/package.xml

Visualforce, CSS, JavaScript, 画像, その他の静的リソースをそれぞれ対応するディレクトリに置いておけば、面倒なことはMiddlemanがやってくれます。

Slim, Sass, CoffeeScriptを使っていますが、HAMLやLESS, JadeやTypeScriptも使えます(設定は必要ですが)。

Visualforce

Middlemanはapex:compositionのような機能があって、/source/pages/layouts/layout.slimと、それぞれのHTMLになるファイルを自動的に合成してくれます。上の表で「なし」「なし」となっているのは、他のページのレイアウトを決めるためのファイルなのでビルドしても意味が無いからです。layout.slimにヘッダを書いておけば、/source/pages/*.slimはbodyの下だけに集中して書くことができます。ちなみにlayout.slimのyieldと書かれている部分にそれぞれのページがはめ込まれます。

/source/pages/Sample.page.slim

---
api_version: 32.0
description: false
label: Sample
controller: false
action: false
extensions: false
title: Sample Visualforce
---

最初のここは.page-meta.xmlとapex:pageの属性の設定です。あとtitleタグ。MiddlemanのFrontmatterという機能を利用しています。layoutにちょっとだけページ固有の情報を入れたいときに便利です。(<head>がlayoutにあるのにtitleどうやって変えればいいの!とか)

- content_for :remote_objects
  apex:remoteObjects
    apex:remoteObjectModel name="Contact" fields="Id,Name,FirstName,LastName,Email,Phone"

この部分はRemoteObject用の設定です。使わないならまるごと消していいです。これもMiddlemanのテンプレートヘルパの機能です。各ページはlayout.slimのyieldにはめ込まれますが、content_forを使ったところだけ別のところにはめ込むことができます。これは2行目のyield_contentのところに入ります。ますますapex:compositionっぽい。

リンクやimg srcは{!$Page.PageName}{!URLFOR($Resource.resoucename, 'path/to/file')}を使います。これと{!$Site.Prefix & $Page.PageName}(これ要らなかった…)これらは開発サーバでも使えるようにしています。他の$Labelなどはデプロイしなければそのままで表示されます。

静的リソース

ビルドするときは/source/staticresources配下の.resource-meta.xmlが基準になります。例えばsample.resource-meta.xmlがあれば、sampleディレクトリをZipで固めてsample.resourceにします。それ以外のディレクトリはassets.resourceにZipで固めます。

ただし、Middlemanの若干おせっかいな機能でハマらないように、CSSは/source/staticresources/cssに、JavaScriptは/source/staticresources/jsに全部置いて、assets.resourceに固めたほうがいいと思います。CSSから参照するフォントや画像もassets.resourceに入れましょう。

CSS

/source/staticresources/css/にSassやLESSを置いておくだけです。(多分)アセットパイプライン機能の影響で、他の場所にCSSを置くとうまく行きません。

Middlemanにはアセットパイプラインという機能があって、BowerでインストールしたCSSやJavaScriptを一つのファイルにまとめることができるのですが、画像やフォントを使ったCSSだと結構地獄です。bootstrap-sf1の画像とフォントだけ使えるように設定しましたが、おとなしく/source/staticresources/fontsや/source/staticresources/iconsなどに手でコピーした方がいいかもしれません。

bootstrap-sf1を使わないならconfig.rbのここのところをバッサリ消してください。

%w(fonts icons).each do |folder|
  dir = Pathname.new File.join bower_components, 'bootstrap-sf1', 'dist'
  Dir[File.join dir, folder, '**', '*'].reject { |f| File.directory? f }.each do |f|
    sprockets.import_asset(Pathname.new(f).relative_path_from(bower_components)) {
      Pathname.new(config[:css_dir]) + '..' + Pathname.new(f).relative_path_from(dir)
    }
  end
end

BowerでインストールしたCSSは、Sassの先頭で

//=require "bootstrap-sf1/dist/css/bootstrap"

のようにして指定すると勝手にくっつけられます。コメント行に=require "..."と書くだけです。(bootstrap-sf1はbower.jsonの記述が間違ってるのでほぼフルパスを書かないとダメ。jQueryは#=require "jquery"でいいんですが…)

JavaScript, RemoteAction, RemoteObject

JavaScriptも/source/staticresources/js/にCoffeeScriptなどを置いておくだけです。(多分)アセットパイプライン機能の影響で、他の場所にJavaScriptを置くとうまく行きません。

JavaScriptではRemoteActionやRemoteObjectをどうするか悩みます。私は開発中だけVisualforce.remoting.Manager.invokeActionSObjectModelのMockを書いたJavaScriptロードするようにしました。

JavaScriptは/lib/apexremote.coffeeで、これはベタ書きのJSONを返すだけのURLを見に行くだけです。SObjectModelはどのオブジェクトでも使えるようにしたかったのですが、ChromeはProxy API(Rubyのmethod_missingみたいなことができる)が無効化されたので、結局1個1個定義することにしました

for sobject in ['Contact', 'Account']
  SObjectModel[sobject] = class extends SObjectModel
    constructor: (props) -> super sobject, props

実装について

Middlemanのカスタム拡張について少しだけ説明されているのでそれを参考にしました。全部/libに入っています。(ディレクトリ構成やRubyの書き方的な参考には全くならないと思います)

visualforce.rb

このクラスには処理の本体はあまりありません。ほとんど他のクラスを使うように登録しているだけです。

  • 標準設定の上書き(layouts_dir, js_dir, css_dirを書き換えている)
  • 一応、この拡張だけで使う設定の登録(色々設定を変えられるようにはなってないと思います)
  • ビルド前処理(before_build)、後処理(after_build)の登録
  • 開発中にHTMLを書き換える機能の登録と、apexremote用のMockAPIエンドポイントの登録(app.configure :development
  • xmlがlayoutを使わないように設定、layout.htmlができないように設定、.page-meta.xmlを自動生成する設定

middleware.rb

開発サーバで使うときにHTMLを書き換える処理はこのクラスでやっています。Rackミドルウェアを利用しています。Nokogiriを使おうと思いましたが&が消えたりするので、ひたすら正規表現で置き換えてるだけです。

  • <head>の最初にapexremote.coffeeを差し込み
  • apex:pageとapex:remoteObjectsの除去
  • {!$Site.Prefix &amp; $Page.xxx}{!$Page.xxx}{!URLFOR($Resource.xxx, 'yyy')}、を開発サーバに向ける
  • doctype htmlの追加とContent-Typeなどの設定

こいつでSObjectModelのサブクラスを定義できるようにしたらよかった。

builder.rb

静的リソースのZip圧縮と、package.xmlの構築をやっています。私はマカーなのでUNF::Normalizerを使っていますが、プロファイルとかのメタデータを扱うまでは多分必要ないです。(プロファイルはファイル名が「なんとかロファイル」とかになるので、Unicode正規化問題に直撃します)

終わりに

まとめとか苦手なので勘弁して下さい。

Visualforceを書くのにも、Middlemanなどの静的サイトジェネレータをお勧めします。Hybrid Localなモバイルアプリでも使えますし、Salesforce以外でも使えます。いつまでSalesforceで消耗してるの?

とは言ってもRemoteActionやRemoteObjectを使うのは辛いです。APIならローカルプロキシ立てるだけなのでそれほど大変ではないんですが… なにかいい方法があったら教えて下さい。

それから、Middlemanはいろいろと消耗することが多かったので、これから裏側の実装までやるならWebPackなど他のツールを使ったほうがいいんじゃないかなあと思います。うまくできたらぜひ公開してください。