単一ページアプリケーション(SPA)により、Web サイトのユーザーに魅力的なエクスペリエンスを提供することができます。開発者は SPA フレームワークを使用してサイトを構築することを望み、作成者は SPA フレームワークを使用して構築されたサイトのコンテンツを AEM 内でシームレスに編集することを望みます。
SPA オーサリング機能には、AEM 内で SPA をサポートするための包括的なソリューションが用意されています。この記事では、React フレームワーク上のシンプルな SPA アプリケーションを紹介し、その設定方法を説明するほか、独自の SPA の運用をすぐに開始する方法についても説明します。
この記事は React フレームワークに基づいています。対応する Angular フレームワークのドキュメントについては、「Angular を使用した AEM での SPA の概要」を参照してください。
この記事では、シンプルな SPA の基本的な機能と、SPA を運用するための最低条件の概要を説明します。
AEM での SPA の動作について詳しくは、次のドキュメントを参照してください。
SPA 内のコンテンツを作成するには、コンテンツを AEM に格納し、コンテンツモデルによって公開する必要があります。
AEM 外で開発された SPA については、コンテンツモデルの契約に準拠していない場合、オーサリングをおこなうことはできません。
このドキュメントでは、React フレームワークを使用して作成されたシンプルな SPA の構造を順を追って解説し、その仕組みを理解した上で独自の SPA に適用する方法を説明します。
サンプルの SPA では、予想される React の依存関係に加えて、追加のライブラリも利用して SPA の作成を効率化できます。
package.json
ファイルは、SPA パッケージ全体の要件を定義します。SPA の動作に対する AEM の最小依存関係を以下に示します。
"dependencies": {
"@adobe/aem-react-editable-components": "~1.0.4",
"@adobe/aem-spa-component-mapping": "~1.0.5",
"@adobe/aem-spa-page-model-manager": "~1.0.3"
}
このサンプルは React フレームワークに基づいているので、package.json
ファイルには必須となる React 固有の依存関係が 2 つあります。
react
react-dom
クライアントライブラリの作成をビルドプロセスの一部として自動化するために、aem-clientlib-generator
が活用されています。
"aem-clientlib-generator": "^1.4.1",
詳しくは、GitHub のこちらのページを参照してください。
aem-clientlib-generator
は、clientlib.config.js
ファイルで次のように設定されています。
module.exports = {
// default working directory (can be changed per 'cwd' in every asset option)
context: __dirname,
// path to the clientlib root folder (output)
clientLibRoot: "./../content/jcr_root/apps/my-react-app/clientlibs",
libs: {
name: "my-react-app",
allowProxy: true,
categories: ["my-react-app"],
embed: ["my-react-app.responsivegrid"],
jsProcessor: ["min:gcc"],
serializationFormat: "xml",
assets: {
js: [
"dist/**/*.js"
],
css: [
"dist/**/*.css"
]
}
}
};
アプリの実際のビルドでは、クライアントライブラリの自動作成用の aem-clientlib-generator 以外に、トランスパイル用に Webpack も使用します。したがって、build コマンドは次のようになります。
"build": "webpack && clientlib --verbose"
ビルドが完了したら、パッケージを AEM インスタンスにアップロードできます。
AEM プロジェクトでは、 AEM プロジェクトアーキタイプを使用します。このアーキタイプは、React または Angular を使用する SPA プロジェクトをサポートし、SPA SDK を使用します。
以前に説明したように依存関係を追加してアプリをビルドすると、AEM インスタンスにアップロードできる SPA パッケージが作成されます。
このドキュメントの次の節では、AEM での SPA 自体の構造と、アプリケーションの動作にかかわる重要なファイルのほか、それらのファイルがどのように連携するのかについて説明します。
シンプルな画像コンポーネントを例として使用していますが、このアプリケーションのコンポーネントはすべて同じ概念に基づいています。
SPA のエントリポイントは index.js
ファイルです。このファイルの内容を以下に示しますが、重要な部分のみに焦点を当てるために簡略化されています。
import ReactDOM from 'react-dom';
import App from './App';
import { ModelManager, Constants } from "@adobe/aem-spa-page-model-manager";
...
ModelManager.initialize().then((pageModel) => {
ReactDOM.render(
<App cqChildren={pageModel[Constants.CHILDREN_PROP]} cqItems={pageModel[Constants.ITEMS_PROP]} cqItemsOrder={pageModel[Constants.ITEMS_ORDER_PROP]} cqPath={ModelManager.rootPath} locationPathname={ window.location.pathname }/>
, document.getElementById('page'));
});
index.js
の主要機能は、ReactDOM.render
関数を活用して、DOM 内でアプリケーションをインジェクトする場所を決めることです。
これはこの関数の標準的な使用方法です。このアプリ例に独自の使用方法ではありません。
コンポーネントテンプレート(JSX など)を使用してコンポーネントを静的にインスタンス化する場合は、モデルからコンポーネントのプロパティに値を渡す必要があります。
アプリをレンダリングすることで、index.js
は App.js
を呼び出します。このファイルの内容を以下に示しています(重要な部分のみに焦点を当てるために簡略化されています)。
import {Page, withModel } from '@adobe/aem-react-editable-components';
...
class App extends Page {
...
}
export default withModel(App);
App.js
の主な役目は、アプリを構成するルートコンポーネントをラップすることです。アプリのエントリポイントはページです。
ページをレンダリングすると、App.js
は以下の Page.js
(簡易表示)を呼び出します。
import {Page, MapTo, withComponentMappingContext } from "@adobe/aem-react-editable-components";
...
class AppPage extends Page {
...
}
MapTo('my-react-app/components/structure/page')(withComponentMappingContext(AppPage));
この例の AppPage
クラスでは、インナーコンテンツメソッドを含む Page
を拡張して使用します。
Page
は、ページモデルの JSON 表現を取得し、コンテンツを処理してページの各要素をラップ/デコレートします。Page
のその他の詳細については、SPA ブループリントのドキュメントを参照してください。
ページがレンダリングされると、以下の Image.js
などのコンポーネントがレンダリング可能になります。
import React, {Component} from 'react';
import {MapTo} from '@adobe/aem-react-editable-components';
require('./Image.css');
const ImageEditConfig = {
emptyLabel: 'Image',
isEmpty: function() {
return !this.props || !this.props.src || this.props.src.trim().length < 1;
}
};
class Image extends Component {
render() {
return (<img src={this.props.src}>);
}
}
MapTo('my-react-app/components/content/image')(Image, ImageEditConfig);
AEMのSPAの中心概念は、SPAコンポーネントをAEMコンポーネントにマッピングし、コンテンツが変更された場合(および逆の場合)にコンポーネントを更新するという考えです。 この通信モデルの概要については、「SPA エディターの概要」を参照してください。
MapTo('my-react-app/components/content/image')(Image, ImageEditConfig);
MapTo
メソッドは、SPA コンポーネントを AEM コンポーネントにマッピングします。単一の文字列または文字列の配列の使用に対応しています。
ImageEditConfig
は、エディターがプレースホルダーを生成するために必要なメタデータを提供することで、コンポーネントのオーサリング機能の有効化に関与する設定オブジェクトです。
コンテンツがない場合は、空のコンテンツを表すラベルがプレースホルダーとして提供されます。
モデルからのデータは、コンポーネントのプロパティとして動的に渡されます。
コンポーネントをエクスポートして編集可能な状態を維持することができます。
import React, { Component } from 'react';
import { MapTo } from '@adobe/aem-react-editable-components';
...
const EditConfig = {...}
class PageClass extends Component {...};
...
export default MapTo('my-react-app/react/components/structure/page')(PageClass, EditConfig);
MapTo
関数は、指定された Component
を、オーサリング可能にするクラス名と属性で拡張した構成結果の PageClass
を返します。このコンポーネントは後でエクスポートし、アプリケーションのマークアップでインスタンス化できます。
MapTo
または withModel
関数を使用してエクスポートされた Page
コンポーネントは、ModelProvider
コンポーネントによってラップされます。このコンポーネントは、標準コンポーネントに、ページモデルの最新バージョンに対するアクセス権またはそのページモデル内の詳細な位置を提供します。
詳しくは、SPA ブループリントのドキュメントを参照してください。
デフォルトでは、withModel
関数を使用するとコンポーネントのモデル全体を受け取ります。
単一ページのアプリケーション内のコンポーネントが情報を共有することは定期的に必要です。これをおこなう推奨方法にはいくつかあり、以下に簡単なものから順に示します。