3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Salesforce B2C Commerceのコントローラを修正・拡張する(append, prepend, replace, ミドルウェア)

Last updated at Posted at 2022-02-02

※ これから記載する事項は、私が所属する会社とは一切関係のない事柄です。

コントローラを修正・拡張する際の方法を紹介します。SFRAの基礎となるapp_storefront_baseを拡張したいときや自作で作ったカートリッジを修正・拡張したいときに便利です。

今回覚えて欲しいこと

  1. カートリッジはカートリッジパスの左が優先
  2. append, prepend, replaceのそれぞれの役割
  3. コントローラのミドルウェアは左のファンクションから実行される

(準備)まずはサンプルに使うコントローラとテンプレートを作る

2つの名前のカートリッジを作成します。カートリッジの作成方法はこちら

  • controller_sample_base(ベース)
  • controller_sample_custom (カスタム)

ベースとしてcontroller_sample_baseに Test.js と言うコントローラを作り、Showという名前のルートを作ります。
Showルートは controller_sample_base カートリッジ内の templates/default/sample/test.isml をテンプレートとしてブラウザに描画します。
さらにcontroller_sample_customに同様のTest.jsと言うコントローラを作り、controller_sample_baseのTest.jsを上書きしながらappend, prepend, replaceの動きを確認していきます。
作成したフォルダ構成は下記の画像の通りです。
image (11).png

ベースに利用するコードとテンプレート

controller_sample_base/cartridge/controller/Test.js
'use strict';

var server = require('server');

server.get('Show', function (req, res, next) {
    res.render('sample/test', {
        param1: "Test",
    }); 
    next();
});

module.exports = server.exports();
controller_sample_base/cartridge/templates/default/sample/test.isml
 <html>
 <body>
   <h1>${pdict.param1}</h1>
   <h1>${pdict.param2}</h1>
   <h1>${pdict.param3}</h1>
 </body>
 </html>

ベースのみで実行した場合

ベースのみのカートリッジで「https://your-env.commercecloud.salesforce.com/on/demandware.store/Sites-YourSite-Site/ja_JP/Test-Show」のようにブラウザからアクセスした場合このような見た目になります。

image (12).png

1.カートリッジはカートリッジパスの左が優先

カートリッジパスは左が優先になります。
関連するドキュメントとしてInfo CenterQiitaがあります。
今回は作成したカートリッジを優先させた上でさらにcustom→baseと言うようにカスタムが優先となるようにカートリッジパスを設定します。

カートリッジパス:
controller_sample_custom:controller_sample_base:その他のカートリッジ...

これで、カスタムカートリッジでTest.jsを上書くor拡張することができます。
例えば、controller_sample_base/cartridge/controller/Test.js
のコードをcontroller_sample_custom/cartridge/controller/Test.jsへコピペして、"Test"
"Test2"へ書き換えた場合、controller_sample_customTest.jsが優先されるため画像のような見た目になります。(ただし本当にベースを完全に置き換えたい場合は、このやり方ではカスタムの内容がベースのような意味合いになるので、ルートを置き換える場合は、後述するreplaceをお勧めします。)
image (13).png

Tips

今回はコントローラの上書きの例でしたが、他にもscripts配下やtempletes配下のファイルも同じディレクトリでかつ同じ名前の場合はカートリッジパスの左側にあるカートリッジのファイルが優先されます。

2.append, prepend, replaceのそれぞれの役割

append, prependはベースカートリッジの挙動を活かしつつ、処理をしたいときに使い、 replaceは文字通りルートを丸っと置き換えたい場合に利用します。ルートの処理はprepend→(get/post/use)→appendの順で処理が走ります。そのため、既存のルートの前に処理を挟みたい場合はprepend、後に挟みたい場合はappendとなります。
get/post/useの違い

append, prependの挙動の確認

実際にappend, prependを使ってみて挙動を確認してみましょう。
下記に実験するコードを記載します。

ベースのTest.jsのコード
==省略==
server.get('Show', function (req, res, next) {
    res.render('sample/test', {
        param1: "Test from get",
        param2: "Test from get",
        param3: "Test from get",
    }); 
    next();
});
==省略==

※ append, prepend, replaceを利用する場合は server.extend(module.superModule)を呼ぶ必要がありますのでご注意ください。

カスタムのTest.jsのコード
"use strict";

var server = require("server");
server.extend(module.superModule);

server.prepend('Show',function (req, res, next) {
    var viewData = res.getViewData();
    viewData.param2 = 'Test from prepend';
    res.setViewData(viewData);
    next();
});

server.append("Show", function (req, res, next) {
    var viewData = res.getViewData();
    viewData.param3 = "Test from append";
    res.setViewData(viewData);
    next();
});

module.exports = server.exports();

上記のコードを実行した場合、このような結果になります。

image (14).png

なぜこのような結果になったかというと、このような順序で処理が行われたからです。

(1) prependの実行
param1 → null
param2 → Test from prepend
param3 → null

(2) getの実行
param1 → Test from get
param2 → Test from prepend をTest from getで上書き
param3 → Test from get

(3) appendの実行
param1 → Test from get
param2 → Test from get
param3 → Test from getをTest from appendで上書き

replaceの挙動の確認

上記の「append, prependの挙動の確認」で使用したベースのTest.jsのコードに対して下記のカスタムコードを利用します。

カスタムのTest.jsのコード
"use strict";

var server = require("server");
server.extend(module.superModule);

server.replace('Show', function (req, res, next) {
    var viewData = res.getViewData();
    viewData.param1 = 'Test from replace';
    res.setViewData(viewData);
    res.render('sample/test');
    next();
});

module.exports = server.exports();

このカスタムコードを実行した場合、画像のようになります。

image (15).png

なぜこのような結果になったかというと、ベースのTest.jsのShowは全く実行されず、カスタムのTest.jsのShowが実行されたためです。

replace はベースが get や post だったとしても置き換えるため、結果的に get と post どっちでもいいようなルートになるのでご注意ください。get のみに制限したい場合は、下記のヘルプのようにミドルウェアを利用します。
https://github.com/SalesforceCommerceCloud/storefront-reference-architecture/blob/master/cartridges/modules/server/README.md#replacing-a-route

追記 2022/6/8

カートリッジが3つ以上の場合の挙動も記載しておきます。

例えば、このようなカートリッジパスがあり、それぞれのカスタムカートリッジにappendもprependもあった場合、
controller_sample_custom1:controller_sample_custom2:controller_sample_base
下記のように1から順番に実行されるようです。

  1. custom1のprepend
  2. custom2のprepend
  3. baseの(get/post/use)
  4. custom2のappend
  5. custom1のappend

replaceを途中で入れた場合も記載しておきます。

例えば、このようなカートリッジパスがあり、カスタムカートリッジにappendもprependもあった場合、
controller_sample_custom:controller_sample_replace:controller_sample_base
下記のように1から順番に実行されるようです。

  1. customのprepend
  2. replaceの(get/post/use)
  3. customのappend

つまり、baseは置き換えられ、完全に実行されなくなります。

3.コントローラのミドルウェアは左のファンクションから実行される

コントローラには本処理以外の処理を挟む事もできます。それをミドルウェアと呼んでいます。

ミドルウェアを作成する。

controller_sample_base/cartridge/scripts/middleware/sample.js
'use strict';

function sampleOne(req, res, next) {
    var viewData = res.getViewData();
    viewData.param1 = viewData.param1 + ' :Test from middleware sampleOne';
    res.setViewData(viewData);
    next();
}

function sampleTwo(req, res, next) {
    var viewData = res.getViewData();
    viewData.param1 = viewData.param1 + ' :Test from middleware sampleTwo';
    res.setViewData(viewData);
    next();
}

module.exports = {
    sampleOne: sampleOne,
    sampleTwo: sampleTwo,
};

ミドルウェアをコントローラに設定。

controller_sample_base/cartridge/controller/Test.js
'use strict';

var server = require('server');
var sample = require('~/cartridge/scripts/middleware/sample');

server.get('Show', sample.sampleOne, sample.sampleTwo, function (req, res, next) {
    var viewData = res.getViewData();
    viewData.param1 = viewData.param1 + ' :Test';
    res.setViewData(viewData);
    res.render('sample/test'); 
    next();
});

module.exports = server.exports();

これらを実行すると、画像のような内容になります。
image (16).png

なぜこうなるかというと、このような順序で処理が行われたからです。

(1) sampleOneの実行
param1 → undifined + :Test from middleware sampleOne

(2) sampleTwoの実行
param1 → param1 + :Test from middleware sampleTwo

(3) 本処理の実行
param1 → param1 + :Test

こちらのヘルプを見たらわかるのですが、
get(name : string, arguments : Array.<function()>)
なので、左から設定されているファンクションが順に実行されます。
そのため、例えば、

Test.js
server.get('Show', sample.sampleOne, function (req, res, next) {
    ==省略本処理==
}, sample.sampleTwo);

みたいな感じで、sampleOneの実行 → 本処理の実行 → sampleTwoの実行のように順番を変えることも可能です。

Tips
外部のモジュールをインポートする際に利用している ~は自ファイルが所属しているカートリッジのみを探索します。その他のカートリッジ内のモジュールを探したい時は *を利用します。
今回の例で言うと、
var sample = require('~/cartridge/scripts/middleware/sample')
または、
var sample = require('*/cartridge/scripts/middleware/sample')

まとめ

append, prependとミドルウェアを合わせた場合の処理の順番を記載します。

  1. prependに設定された左からの関数が順に実行される
  2. get/post/useに設定された左からの関数が順に実行される
  3. appendに設定された左からの関数が順に実行される

ミドルウェアがあるからappendとかprependいらないんじゃ?と思う方いらっしゃるかも知れませんが、SFRAの作りとしてはベースのコードは修正せずにカスタムして行った方がいいです。
appendやprependはベースとなっているコードを一切触らずに処理を追加できるので非常に便利です。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?