Visualforce Page / ComponentをReactで作る

Visualforce PageとComponentを使って、 ReactアプリケーションをSalesforce上で動作させる方法について記載しました。 webpackとSalesforceの静的リソースを使うことでReactを導入できます。
2021.02.03

SalesforceのVisualforce Componentの作成をReactを使って行ってみました。 Vue.jsやAngularなども同様の方法で使えると思います。

Salesforceの環境にDeveloper Edition組織を使っています。

Visualforce Page / Componentの骨組みを作る

VSCodeのコマンドパレットで[SFDX: Create Visualforce Page]を実行してVisualforce Pageの骨組みを作ります。 ここではページ名を[ReactSampleVFPage]にしました。

Visualforce Pageの骨組み

force-app/main/default/pages/ReactSampleVFPage.page*が作成されました。

続いて、コマンドパレットで[SFDX: Create Visualforce Component]を実行してVisualforce Componentの骨組みを作ります。 ここではコンポーネント名を[ReactSampleVFComponent]にしました。

Visualforce Componentの骨組み

force-app/main/default/components/ReactSampleVFComponent.component*が作成されました。

Hello, Worldを作ってデプロイする

force-app/main/default/pages/ReactSampleVFPage.pageを選択して、下記に変更します。

<apex:page>
<!-- Begin Default Content REMOVE THIS -->
<h1>Congratulations</h1>
<c:ReactSampleVFComponent />
<!-- End Default Content REMOVE THIS -->
</apex:page>

続いて、force-app/main/default/components/ReactSampleVFComponent.componentを選択して、下記に変更します。

<apex:component>
<!-- Begin Default Content REMOVE THIS -->
<h1>Congratulations</h1>
Hello, World.
<!-- End Default Content REMOVE THIS -->
</apex:component>

変更したら、force-app/main/default/を選択した状態で[SFDX: Deploy Source to Org]を実行して、ReactSampleVFPageとReactSampleVFComponentを組織にデプロイします。

デプロイできたら組織にログインし、[設定] > [Visualforceページ] > [ReactSampleVFPage]に移動して、[Lightning Experience、Lightning コミュニティ、およびモバイルアプリケーションで利用可能]にチェックを入れます。

Visualforceページの設定画面

続いて任意のアプリケーション(ここでは[サービス]を選択)のホームに移動し、画面右上の歯車アイコンをクリックして[編集ページ]をクリックします。

Salesforce編集ページへのアクセス

左メニューのVisualforceをドラッグアンドドロップでページ内の任意の箇所に配置し、画面右の[Visualforceページ名]に[ReactSampleVFPage]を指定して保存します。

Salesforceのページ編集画面

ホーム画面に「Hello, World」と表示されるようになることを確認します。

Salesforceのホーム画面

ReactでVisualforce Componentを作る

webpackで1ファイルにまとめたbundle.jsを作成し、index.htmlにてこのbundle.jsを読み込んでReactが動作するようにします。 手順は大筋で参考にさせていただいた「Babelとwebpackを使ってES6でReactを動かすまでのチュートリアル」の記事の通りですので、この記事では簡単に手順を示すに留めます。

下準備

$ mkdir react-sample
$ cd react-sample
$ git init
$ npm init -y
$ mkdir src dist

ここではプロジェクトディレクトリを[react-sample]としています。

babelとwebpackの設定

$ npm i -D @babel/core babel-loader @babel/preset-env @babel/preset-react @babel/register
$ cat > .babelrc
{
  "presets": [
    "@babel/preset-env", "@babel/preset-react"
  ]
}
$ npm i -D webpack webpack-cli
$ npm i -D webpack-dev-server html-webpack-plugin
$ cat > webpack.config.js
require('@babel/register');
module.exports = require('./development');
$ cat > development.js
import path from 'path'
import HtmlWebpackPlugin from 'html-webpack-plugin'

const src  = path.resolve(__dirname, 'src')
const dist = path.resolve(__dirname, 'dist')

export default {
  mode: 'development',
  entry: src + '/index.jsx',

  output: {
    path: dist,
    filename: 'bundle.js'
  },

  module: {
    rules: [
      {
        test: /\.jsx$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  },

  resolve: {
    extensions: ['.js', '.jsx']
  },

  plugins: [
    new HtmlWebpackPlugin({
      template: src + '/index.html',
      filename: 'index.html'
    })
  ]
}

Reactパッケージのインストール

$ npm i -S react react-dom
$ cat > src/index.jsx
import React from 'react';
import {render} from 'react-dom';

class App extends React.Component {
  render () {
    return <p> Hello React!</p>;
  }
}

render(<App/>, document.getElementById('root'));
$cat > src/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>React Test</title>
  </head>
  <body>
    <div id="root"/>
  </body>
</html>

動作確認

./node_modules/.bin/webpack-dev-server

上記コマンドでWebサーバを立ち上げ、http://localhost:8080/にアクセスします。 画面に「Hello, React!」と表示されることを確認します。

Hello Reactの画面

Visualforce ComponentにReactアプリケーションを組み込む

webpackを実行してbundle.jsindex.htmlを作成します。

$ ./node_modules/webpack/bin/webpack.js
$ ls dist
bundle.js   index.html

Salesforceの静的リソースに登録するために、zipに固めます。

$ cd dist
$ zip ReactSample.zip bundle.js index.html

作成したzipファイルを静的リソースに設定します。

Salesforceの静的リソース設定画面

静的リソース名は[ReactSample]としました。

次にforce-app/main/default/pages/ReactSampleVFPage.pageを下記に変更します。

<apex:page showHeader="false" standardStylesheets="false" title="ReactSample" id="page">
  <meta charset="utf-8"/>
  <title>ReactSample Page</title>
  <c:ReactSampleVFComponent />
  <script src="{!URLFOR($Resource.ReactSample, 'bundle.js')}"></script>
</apex:page>

先に作成した静的リソースのbundle.jsを{!URLFOR($Resource.ReactSample, 'bundle.js')}で参照しています。 <script>タグはコンポーネントの呼び出し(<c:ReactSampleVFComponent />)より後にする必要がありますので注意してください。

続いてforce-app/main/default/components/ReactSampleVFComponent.componentを下記に変更します。

<apex:component>
<!-- Begin Default Content REMOVE THIS -->
<div id="root"></div>
<!-- End Default Content REMOVE THIS -->
</apex:component>

id="root"のdivタグに対してReactが稼働します。

変更したページとコンポーネントを組織にデプロイしたら、組織にログインして任意のアプリケーション(ここでは[サービス]を選択)のホームに移動します。 「ReactSampleVFPage」の箇所に「Hello, React!」と表示されていれば成功です。

Hello, React!が表示されているホーム画面

ちょっとだけ複雑なReactを動かしてみる

Hello, Worldだけではつまらないので、ちょっとだけ複雑なReactも動かしてみます。

src/index.jsxを次に変更します。

import React from 'react';
import {render} from 'react-dom';
import Clock from './Clock';

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = { name: 'somebody' }
  }

  onChange(e) {
     this.setState( {name: e.target.value} )
  }

  render() {
    return (
      <div>
        <p>お名前: <input type="text" onChange = { this.onChange.bind(this) } /></p>
        <p>{ this.state.name }さん、こんにちは!</p>
        <Clock />
      </div>
    )
  }
}

render(<App/>, document.getElementById('root'));

また、src/Clock.jsxを下記の内容で作成します。

import React from 'react';
import {render} from 'react-dom';

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        現在時刻は{this.state.date.toLocaleTimeString()}です。
      </div>
    );
  }
}
export default Clock;

名前の入力を受け付けて、入力された名前に対して挨拶しつつ現在時刻を伝えるReactアプリケーションです。

ちょっと複雑なReactアプリケーション

再度、webpackを実行し、成果物をzipに固めます。

$ ./node_modules/webpack/bin/webpack.js
$ ls dist
bundle.js   index.html
$ cd dist
$ zip ReactSample.zip bundle.js index.html

静的リソースReactSampleを新しく作成したReactSample.zipで置き換えて、再度アプリケーション(ここでは[サービス]を選択)のホームに移動します。 作成したReactアプリケーションがSalesforce上で動作していることが確認できます。

Salesforce上で動作するちょっと複雑なReactアプリケーション

まとめ

Visualforce PageとComponentを使って、 ReactアプリケーションをSalesforce上で動作させる方法についてみてきました。 開発したReactアプリケーションをwebpackでまとめれば、あとはそれをSalesforce上で静的リソースとして設定することでVisualforceにて動作させることが可能です。Lightning Web Componentだけでなく、Reactなどの3rd partyの仕組みを使ってもSalesforceの機能を作ることができます。

選択肢の一つとして道具箱に入れてみてください。

参考資料