react-lightning-design-systemをVisualforceで使う
件名の通りですが少しハマりどころがあったのでメモ。
普通に react-lightning-design-system を使おうとすると SVG アイコン使用時に Unsafe attempt to load URL エラーが表示されます。
Unsafe attempt to load URL https://zakiyama-dev-ed.my.salesforce.com/assets/icons/utility-sprite/svg/symbols.svg#event from frame with URL https://zakiyama-dev-ed--c.ap2.visual.force.com/apex/LDSTest. Domains, protocols and ports must match.
いわゆる CORS: Cross Origin Resource Sharing、つまり salesforce.com
と visual.force.com
という異なるオリジンでリソースのやり取りをしようとしているのが原因です。
Issue にもあります。
これを回避するためにはコメントにもあるように、util.setAssetRoot(path)
というメソッドを使います。
また setAssetRoot
に渡す静的リソースへのパスは、Visualforce 側で
{!URLFOR($Resource.slds)}
を使い取得します。
Visualforce ページと、React 側のエントリーポイントとなる index.js は以下のようになります。
<apex:page showHeader="false" standardStyleSheets="false" docType="html-5.0"> <html> <head> <meta charset="utf-8" /> <apex:stylesheet value="{!URLFOR($Resource.slds,'assets/styles/salesforce-lightning-design-system.min.css')}" /> </head> <body> <div id="root"></div> <apex:includeScript value="{!URLFOR($Resource.app, 'bundle.js')}" /> <script type="text/javascript"> App.init(document.getElementById('root'), '{!URLFOR($Resource.slds)}/assets'); </script> </body> </html> </apex:page>
// src/scripts/index.js import React from 'react'; import { render } from 'react-dom'; import { util } from 'react-lightning-design-system'; import App from './components/App'; import '../stylesheets/index.scss'; export const init = function(el, assetRoot) { util.setAssetRoot(assetRoot); console.log('Set asset root as ', util.getAssetRoot()); render(<App />, el); };
この例では、webpack.config.js の設定でビルドした JavaScript を App
というライブラリ名でエクスポートしてます(参考)
また、初期化用関数の引数として渡してますが
window.assetRoot = '{!URLFOR($Resource.slds)}/assets';
のように window 関数に直接変数を生やして index.js 側で参照するという方法もあります。
余談:<script> タグと <apex:includeScript> タグの読み込み順序に関して
上の例では、<apex:includeScript>
による JS 読み込みが続く <script>
タグ内のスクリプトより先に実行される必要がありますが
https://developer.salesforce.com/docs/atlas.ja-jp.204.0.pages.meta/pages/pages_compref_includeScript.htm
を見ると、loadOnReady
を明示的に true にしない限り <apex:includeScript>
の方がすぐに読み込まれるようです。
(すぐに、というのが曖昧ですが、一旦こちらの読み込みが先行すると考えておいて良さそうです)
Spring'17 で追加された <apex:slds> を使う場合
Spring'17 から Visualforce ページでも、静的リソースにアップロードすることなくプラットフォームから提供される SLDS が使えるようになります。
参考:Visualforce ページでの Lightning Design System の使用
これを使う場合、先ほどのコードは
<html> <head> <meta charset="utf-8" /> - <apex:stylesheet value="{!URLFOR($Resource.slds,'assets/styles/salesforce-lightning-design-system.min.css')}" /> + <apex:slds /> </head> <body> <div id="root"></div> <apex:includeScript value="{!URLFOR($Resource.app, 'bundle.js')}" /> <script type="text/javascript"> - App.init(root, '{!URLFOR($Resource.slds)}/assets'); + App.init(root, '{!URLFOR($Asset.SLDS)}/assets'); </script> </body> </html>
というように、静的リソースから SLDS を読み込んでいた部分を <apex:slds />
に置き換え、さらにパス部分は $Asset.SLDS
という変数を使って取得するようにします。
(2017/02/20追記)
バージョンは最新バージョンの 2.2.1 でなく 2.1.3 のよう。
あと読み込んでいるリソースが salesforce-lightning-design-system-vf.min.css なのも気になる。
(追記ここまで)
さらに、applyBodyTag
または applyHtmlTag
が false だった場合
リリースノートおよび開発者ガイドの該当ページによると、
Using the Lightning Design System | Visualforce Developer Guide | Salesforce Developers
if you set applyBodyTag or applyHtmlTag to false, you must include the scoping class slds-scope
とあるので、
- <body> + <body class="slds-scope">
などとする必要があります。
(そういう意味で、上の例では applyBodyTag および applyHtmlTag を false にしていないのに <html> や <body> を使っているのは適切ではないですね)
おまけ:<apex:slds> の既知のバグ
検証中、<apex:slds> を使うと最初の Unsafe attempt to load URL エラーが発生することがわかりました。
どうやら $Asset.SLDS
の URL が salesforce.com になっており、調べてみると StackExchange にありました。バグのようです。
近日中に修正されるようなので1週間後ぐらいに確認してみることとします。
(2017/02/20追記)
今日見たら直ってました。
StackExchange のコメントに更新はなく、Known Issues にもないので、動きを確認した限りですが。
(追記ここまで)