当サイトを最適な状態で閲覧していただくにはブラウザのJavaScriptを有効にしてご利用下さい。
JavaScriptを無効のままご覧いただいた場合には一部機能がご利用頂けない場合や正しい情報を取得できない場合がございます。
承知しました
本サイトではWebサイトのエクスペリエンスを向上させるために、Cookieを使用しています。Cookieはブラウザの設定から無効にできます。本サイトで使用するCookieについては、プライバシーポリシーをご確認ください。

Blog

ブログ

開発者向け

AngularとScullyでJamstackサイトを構築する方法

By Ondrej Polesny  

GatsbyとNext.jsの機能開発レースは、React開発者であれば注目に値しますが、Angularの友達はどうでしょうか? Scullyは、Angularの最初で唯一の静的サイトジェネレーターですが、他のジェネレーターとはまったく異なる動作をします。

この記事では、AngularでWebサイトを実装し、Scullyをインストールし、すべてのページを静的ファイルとして生成するように構成する方法について説明します。また、展開オプションと他のジェネレーターとの比較についても触れます。

Angularとは何ですか?開発者がAngularを使用する理由

Angularは、ReactやVueとともに最も使用されているJavaScriptフレームワークの1つです。客観的に最も急な学習曲線のため、Angularを選択する初心者は多くありません。同じ理由で、最初のAngularサイトを実装するのに数年かかり、そのプロセスをまったく楽しんでいませんでした。ただし、時間の経過とともに、厳密に定義されたプロジェクト構造、TypeScriptの優れたサポート、モジュラースコープなどのAngularの利点がわかり始めます。 Angularに慣れている場合は、提示されたすべてのプロジェクトにすばやく慣れることができます。これが、Angularが大規模なプロジェクトやエンタープライズアプリで非常に人気がある理由です。

スカリーとは

Scullyは、Angularの静的サイトジェネレーターです。以前にJamstackサイトを実装したことがあり、SSGがどのように機能するかを知っていると思われる場合は、そこで停止させてください。 Scullyの動作はまったく異なります。通常、静的サイトジェネレーターはフレームワークとプロジェクト構造を提供し、その上にアプリを構築します。スカリーではありません。 Scullyは、コンパイルされたAngularアプリを入力として受け取り、URLのリストを提供するように求めます。静的ファイルとして生成するパス。次に、アプリを実行し、すべてのパスをヒットし、出力をHTMLファイルに保存します。

Angularを使用したJamstackアプリの構築

では、AngularでJamstackアプリを構築するにはどうすればよいでしょうか?最初に、間違いなく遭遇するAngularの基本のいくつかを説明してから、Scullyを構成する方法を示します。この記事で説明されているすべてのコンポーネントには、 GitHubでの実装へのリンクが含まれています。

モジュールとコンポーネント

Angularには、モジュールとコンポーネントの概念が付属しています。コンポーネントには、メニュー、ヘッダー、フッター、スコープ付きのコードおよびロジックを含めることができます。コンポーネントは常にモジュールに属している必要があります。モジュールには通常、相互に関連するコンポーネントと特定の機能に関連するコンポーネントが含まれています。 ComponentsModuleAppRoutingModule 、ScullyLibModule、またはAppModuleと同様に、これらはすべてのAngularアプリのメインモジュールです。

Kentico Kontent

デフォルトでは、すべてのコンポーネントは、親モジュールのスコープ内でのみ使用できます。モジュール外のコンポーネントを使用する場合、モジュールはそれをエクスポートする必要があります。

...
@NgModule({
declarations:[SidebarComponent,ArticleComponent,MenuComponent,LinksComponent],
imports:[
CommonModule,
RouterModule
],
exports:[
SidebarComponent,
ArticleComponent,
MenuComponent,
LinksComponent
]
})
exportclassComponentsModule{}

だから我々は我々のアプリでメニューコンポーネントを使用したい場合は、我々はまずAppModuleComponentsModuleをインポートし、メニューコンポーネントをエクスポートし、我々はそれをレンダリングする場所でその輸入MenuComponentは後にするComponentsModuleを必要としています。

ルーティングモジュール

ルーティングは、 AppModuleインポートされる特別なモジュールAppRoutingModuleによって処理されます。次のコマンドを使用して作成できます。

ng generate module app-routing —flat —module=app

すべてのルートは、出力のレンダリングを担当するURLパスとコンポーネントを定義する必要があります。 https://your.domain/articles/url-of-the-article下にブログ投稿ページを作成する場合、ルートは次のようになります。

{path: '/articles/:slug', component: FullArticleComponent}

FullArticleComponentをインポートする必要AppRoutingModuleに。 /articles/始まるURLへのすべてのリクエストは、FullArticleComponentに転送されます。

コンポーネントとテンプレートの基本

モジュールとルーティングが配置されたので、コンポーネントとレンダリングマークアップに進むことができます。 コンポーネントは、目的に応じて複数のファイルに分割されます。これらは通常、ロジック(コード)とマークアップを備えています。

./article.component.ts
./article.component.html

ただし、他のファイルも存在する可能性があります。たとえば、スタイル定義用の.sassファイル。

./article.component.sass

次のコマンドを使用してコンポーネントを生成できます。

ng generate component Article --module ComponentsModule

コンポーネントのコード部分は、レンダリング用のデータを準備する役割を果たします。データの収集と変換のための関数が含まれています。私の場合、ほとんどの場合、Kentico Kontentからデータを収集するサービスと、Moment.jsライブラリを使用して日付を変換しました。

...
@Component({
selector:'full-article',
templateUrl:'./full-article.component.html'
})
exportclassFullArticleComponentimplementsOnInit{
constructor(protectedkontentService:KontentService,...){
this.moment=moment;
}
...
public article: Article;
moment:any;
...
ngOnInit():void{
this.loadData();
...
}
loadData():void{
this.kontentService.deliveryClient
.item('author')
...
.then(response=>{
this.article=response.items[0];
})
}
}

コンポーネントクラスのすべてのパブリックメンバーは、マークアップで使用できます。

マークアップ部分に関しては、プラットフォームごとにレンダリングデータの処理が異なります。しかし、私たちはほとんどの場合、同じ目標を達成しようとしています。 Angularのリファレンスについては、以下の表をご覧ください。


構文
データをHTMLに出力する{{ variable }} {{ metadata.subtitle.value }}
HTML属性へのデータの出力< ... [attr.name]='variable'>
データセットの反復< ... *ngFor='let item of collectionVariable'>
条件付きマークアップのレンダリング
クラス属性の追加
子コンポーネントへのデータの受け渡し

エントリコンポーネントは通常、 AppRoutingModule のデフォルトの空のルート定義されているHomeComponentです。 HTMLベースフレームは、プロジェクトルートの下のsrcフォルダーにあるindex.htmlファイルにあります。

DeliveryClientと環境ファイルの追加

では、実際のコンテンツをプロジェクトに追加するにはどうすればよいでしょうか。

少し上で収集したデータについてはすでに触れました。外部とのコミュニケーションには、環境ファイルが必要です。 APIエンドポイントのURL、アクセスキー、および一般的に環境によって異なる可能性のある機密データを保存するために使用されます。最も一般的には、 .envと呼ばれ、プロジェクトのルートに配置されます。 Angularでは、ファイルはenvironment.ts呼ばれ、別のフォルダー/environments保存されます。

このファイルを使用して、サイトで使用するすべてのコンテンツを含むKontentプロジェクトのプロジェクトIDを保存しました。

exportconstenvironment={
production:false,
kontent:{
deliveryProjectId:'fe1e198a-96eb-01ea-a4c8-477c331d5ed8'
}
};

外部とのコミュニケーションを促進するために、注入可能なヘルパークラス(サービス)を作成できます。 KontentSDKのDeliveryClientをインスタンス化するKontentServiceを作成しました。

@Injectable({
providedIn:'root'
})
exportclassKontentService{
publicdeliveryClient:IDeliveryClient;
constructor(angularHttpService:AngularHttpService){
this.deliveryClient=newDeliveryClient({
projectId:environment.kontent.deliveryProjectId,
httpService:angularHttpService,
typeResolvers:[
newTypeResolver('article',()=>newArticle()),
newTypeResolver('author',()=>newAuthor()),
newTypeResolver('category',()=>newCategory()),
newTypeResolver('content_page',()=>newContentPage()),
newTypeResolver('menu_item',()=>newMenuItem()),
newTypeResolver('site_metadata',()=>newSiteMetadata()),
newTypeResolver('tag',()=>newTag())
]

})

}

}

標準のHttpServiceの代わりにAngularHttpServiceを使用していることに注意してください。そうしないと、Angularは、サイトを事前レンダリングするときにすべてのデータがフェッチされるまで待機しません。

インジェクタブルサービスは、コンポーネントクラスのコンストラクターに追加することで、コンポーネントのどこからでも使用できます。

constructor(protectedkontentService:KontentService,...){
this.moment=moment;
}

protectedキーワードは自動的にクラスメンバーを作成するため、クラス関数でサービスを直接使用できます。

loadData():void{
this.kontentService.deliveryClient
.item('author')
...
.then(response=>{
this.article=response.items[0];
})
}

異なるURLで同じコンポーネントにルーティングする

ngOnInit関数でデータを取得するのが最適です。ただし、コンポーネントが複数のURLを処理する場合は、ナビゲーションイベントをサブスクライブし、データ収集を手動でトリガーして、コンポーネントが正しいデータをレンダリングするようにする必要があります。次の2つのURLを見てください。

https://your.domain/articles/angular-is-cool
https://your.domain/articles/scully-is-cool

これらは両方とも、コンポーネントFullArticleComponentによって処理されます。最初の記事は正しくレンダリングされますが、リンクをクリックして他の記事を表示すると、AngularはngOnInit関数をトリガーせず、更新を取得しません。これを軽減するには、ナビゲーションイベントに登録していることを確認してください。

ngOnInit(): void {
this.loadData(); // load data in extra function
this.router.events.pipe(
filter((event: RouterEvent) => event instanceof NavigationEnd)
).subscribe(() => {
this.loadData();
});
}

注:これはAngular機能です。 Angularバンドルなしで静的サイトのみを使用する場合は、Scullyがすべてのパスに個別にヒットするため、これをスキップできます。

サイトを事前レンダリングするようにScullyを構成する

上記のすべての手順は、Angularサイトを作成する方法を説明しています。他の静的サイトジェネレーターとは異なり、Scullyはビルドされたサイト上で実行され、すべてのパスにアクセスし、生成されたソースコードをHTMLファイルに保存します。あなたはそれがたどるべき道が何であるかをスカリーに伝える必要があるだけです。

AppRoutingModuleで定義したルートは動的です。たとえば、パス'/articles/:slug'は、ヘッドレスCMSに追加した数に応じて、1つ、2つだけでなく、1万の記事にも一致します。 Scullyは、すべてのリンクとページを見つけようとしてWebサイトをクロールしていませんが、事前にレンダリングするすべてのパスをリストする必要があります。

Scullyをプロジェクトに追加しましょう:

ng add @scullyio/init

そして、 scully.{project name}.config.tsという設定ファイルを開きます。重要な部分はルートの定義です。事前レンダリングするAppRoutingModuleで定義されたすべてのルートのパスのリストを提供する必要があります。ルートがAppRoutingModuleでの定義と完全に一致しない場合、そのルートは無視されます。

データを取得する場所に応じて、Scullyのパスを生成する方法は複数あります。ここでは、ヘッドレスCMSを使用し、APIからデータを取得しています。 resultsHandler関数を使用してAPI応答を処理してから、各アイテムのURLスラッグを保持するプロパティを指定する必要があります。生データを表示するには、ブラウザにAPIURLをコピーして貼り付けてください。

exportconstconfig:ScullyConfig={
projectRoot:'./src',
...
routes:{
...
'/articles/:slug':{
type:'json',
slug:{
url:`https://deliver.kontent.ai/fe1e198a-96eb-01ea-a4c8-477c331d5ed8/items?system.type=article&elements=slug`,
property:'elements.slug.value',
resultsHandler:(raw)=>raw.items
}
}
}
};

事前にレンダリングされたサイトの生成

Scullyは構築されたAngularサイト上で実行されるため、最初に実行する必要があります。

ng build

そして、Scullyを実行します。

npm run scully

これは、すべてを正しく実装および構成した場合に機能します。しかし、それは最初の試みではほとんど起こりません。 :-)では、開発中に最も頻繁に使用するコマンドを見てみましょう。

以下を使用して、ホットリロードでAngularアプリをテストできます。

ng serve

サイトの準備ができたら、それを構築することを忘れないでください。次に、Scullyのルートを構成しようとしているときに、次のコマンドを実行できます。

npm run scully -- -- scanRoutes

これにより、Scullyはルート構成を確認し、生成されたルートを/dist/static/assets/scully-routes.json保存して、リストが期待どおりかどうかを確認するように求められます。

そして最後に、Scullyサーバーを実行して、事前にレンダリングされたページを確認できます。

npm run scully:serve

生産のためのスカリーサイトの構築

事前にレンダリングされたサイトも完全なAngularバンドルをダウンロードしていることに気付いたかもしれませんが、準備が整うと、サイトは突然完全なAngularSPAになります。これは意図された動作です。 Scullyは、バンドルが後でダウンロードされる間、訪問者ができるだけ早くサイトのコンテンツを表示できるようにします。

この動作を変更して静的ファイルのみを保持したい場合は、Angularを無効にするプラグインをインストールできます。

npm i scully-plugin-disable-angular

そして、これらの行をscully.{project name}.config.tsます:

const{DisableAngular}=require('scully-plugin-disable-angular');
const postRenderers = [DisableAngular];
setPluginConfig(DisableAngular, 'render', { removeState: true });
exportconstconfig:ScullyConfig={
defaultPostRenderers:postRenderers,//forallroutes
...
}

サイトを本番用に準備するには、次のことを行う必要があります。

  • 環境ファイルのクローンを作成し、environment.prod.tsという名前を付けenvironment.prod.ts
  • prodフラグを使用してビルドを実行します。

npm run build – --prod && npm run scully

scully.{project name}.config.tsファイルで定義され、デフォルトでは/dist/staticです。ホスティングプラットフォームに公開ディレクトリを設定することを忘れないでください。 Netlifyにデプロイしている場合は、ビルドとデプロイのセクションのビルド設定の下に設定があります

Kentico Kontent

結論

以上です。ビルドが完了すると、サイトはライブで事前に生成されます。

ビルドプロセスの一部で行き詰まったり、有利なスタートを切りたい場合は、GitHubのKontentリポジトリのScullyスターターサイトを確認してください。がんばろう! :)

Headless CMSの導入をお考えでしょうか?

クラウドとマルチデバイスに最適化されたKentico Kontentをお試しください