メタ言語の続きです。前回はEJSのinclude()
を使ってBEMのブロックごとにモジュール分割しました。今回はコンテンツ(見出し、本文など)部分を外部JSONファイルにしてfs
モジュールを使って読み込みます。それではいきましょう!
【目次】
- 前回のおさらい
- index.ejsにコンテンツ部分がある
- _contents.ejs作成:コンテンツのモジュール化(うまくいかず…)
- _contents.ejsの関数化(うまくいかず)
- fsモジュールJSONファイルを読み込む
- index.ejsでJSONデータをリンク(コンテンツ部分は削除)
- JSONデータを打ち替えてみる
- 最後に:データとテンプレートの分離
※参考:前回記事
【EJS】include()でBEMのブロックごとにモジュール分割(Sass(SCSS)も修正) - クモのようにコツコツと
※参考:【メタ言語】HTMLテンプレートエンジン、AltCSS、AltJSのまとめ
qiita.com
前回のおさらい
EJSファイルもBEMのブロックごとにモジュール分割した!
Sass(SCSS)と同じような構成になった。
コンパイルすると「index.html」1ファイルになる。
モジュールはファイル名に_
を付けるとパーシャル化。
ブラウザではこのような表示。 ヘッダー、フッターを新規モジュールとして追加した。
詳細は前回記事を参照
※参考:前回記事
【EJS】include()でBEMのブロックごとにモジュール分割(Sass(SCSS)も修正) - クモのようにコツコツと
index.ejsにコンテンツ部分がある
ただ、前回宿題にした課題がある。
「index.ejs」のコード
<% let header = { title:'クモのようにコツコツと ~別館~', text: 'イイダリョウのメタ言語実験室' } let main = { title:'メタ言語同時コンパイル(EJS、Sass(SCSS)、TypeScript)', lede: 'CodepenのSettingの旅CSS編第2回。今回はSCSS。LESSに似てる?', link: '別ページへ' } let inner = [ { title:'ネスト(階層化)', text: 'SCSSもdivのネストの階層化ができる。この段落は<em class="inner__text-em">ネスト</em>でスタイルを当てている。<br>ん?この書き方はLESSとほぼまったく同じだ。<br><span class="inner__text--bikou">インデントについても厳密でじゃでなくても解釈してくれるようだ(?)</span>' }, { title:'親セレクタの参照', text: 'おそらくLESSにはないSCSS専用の機能。ネストして「&」に擬似要素を付けると親セレクタを参照できる<br><span class="inner__text-bikou">この尾行欄につけている「※」印は「.bikou:before」ではなく「&:before」だけで済んでいる。 </span>' }, { title:'TypeScriptで書いたクリックイベント', text: '<button class="inner__text-btn">ここ押せワンワン</button>' } ] let footer = { text:'© 2020 イイダリョウ' } %> <!doctype html> <html> <head> <%- include('base/_head', {main: main}); %> </head> <body> <%- include('block/_header', {header: header}); %> <%- include('block/_main', {main: main, inner: inner}); %> <%- include('block/_footer', {footer: footer}); %> </body> </html>
後半のタグ部分はモジュール読み込みによってかなりスッキリしたが、前半にコンテンツ(見出しや本文など)部分が残っている。
ここも別ファイルに切り出したい。
_contents.ejs作成:コンテンツのモジュール化(うまくいかず…)
まず最初に試みたのは、冒頭のコンテンツ部分のモジュール化。
_contents.ejsを作成
「contents」フォルダを作って「_contents.ejs」を作成。
「_contents.ejs」にコンテンツ部分を記述
<% let header = { title:'クモのようにコツコツと ~別館~', text: 'イイダリョウのメタ言語実験室' } let main = { title:'メタ言語同時コンパイル(EJS、Sass(SCSS)、TypeScript)', lede: 'CodepenのSettingの旅CSS編第2回。今回はSCSS。LESSに似てる?', link: '別ページへ' } let inner = [ { title:'ネスト(階層化)', text: 'SCSSもdivのネストの階層化ができる。この段落は<em class="inner__text-em">ネスト</em>でスタイルを当てている。<br>ん?この書き方はLESSとほぼまったく同じだ。<br><span class="inner__text--bikou">インデントについても厳密でじゃでなくても解釈してくれるようだ(?)</span>' }, { title:'親セレクタの参照', text: 'おそらくLESSにはないSCSS専用の機能。ネストして「&」に擬似要素を付けると親セレクタを参照できる<br><span class="inner__text-bikou">この尾行欄につけている「※」印は「.bikou:before」ではなく「&:before」だけで済んでいる。 </span>' }, { title:'TypeScriptで書いたクリックイベント', text: '<button class="inner__text-btn">ここ押せワンワン</button>' } ] let footer = { text:'© 2020 イイダリョウ' } %>
_contents.ejsをインクルード
「index.ejs」からinclude()
メソッドで「_contents.ejs」をインクルードする
<%- include('contents/_contents'); %> <!doctype html> <html> <head> <%- include('base/_head', {main: main}); %> </head> <body> <%- include('block/_header', {header: header}); %> <%- include('block/_main', {main: main, inner: inner}); %> <%- include('block/_footer', {footer: footer}); %> </body> </html>
見た目はスッキリ!素晴らしい!
挙動確認(エラー!)
が、この状態でコンパイルするとうまく行かなかった。。
ターミナルでcd
コマンドでフォルダに移動
cd /(パス)/gulp_frontend
gulpを起動
npx gulp
するとこんなエラーが…
ReferenceError in plugin "gulp-ejs" Message: /(パス)/gulp_frontend/src/ejs/index.ejs:6 4| <html> 5| <head> >> 6| <%- include('base/_head', {main: main}); %> 7| </head> 8| <body> 9| <%- include('block/_header', {header: header}); %> main is not defined
_head.ejs
に渡す変数main
が見つからないよ、と。。
どうやら変数はindex.ejs
上にないとダメっぽい。一つ目だけ表示されてるけど、他の変数も同じことでしょうな。
_contents.ejsの関数化(うまくいかず)
次に試したのは _contents.ejsの関数化。下記の記事のような方法。
※参考:テンプレートエンジンEJSで使える便利な構文まとめ - Qiita
※参考:EJSでテンプレートとデータでファイルを切り分けた開発を行う | 大阪市天王寺区SOHOホームページ制作 | デザインサプライ-DesignSupply.-
こんな感じでコンテンツ部分を_contents.ejs
の連想配列(オブジェクト)を関数化する。
<% contents = function () { return { header: { title:'クモのようにコツコツと ~別館~', text: 'イイダリョウのメタ言語実験室' } // 中略 }, } %>
- データ全体をグローバル変数
contents
で囲う contents
の値をfunction
宣言で関数化する- 中のコンテンツ部分を
return
で返す - 変数だった
header
などは全て連想配列(オブジェクト)のキーにする
インクルード部分はオブジェクト形式にする。
<head> <%- include('base/_head', {main: contents.main}); %> </head> <!-- 後略 -->
include ()
メソッドの第2引数をcontents.main
など、contents
オブジェクトのプロパティとして読み込む
しかしこの方法は自分の場合はエラーになり、うまくいかなかった。。
fsモジュールJSONファイルを読み込む
fsモジュールを読み込む
先ほどのこの記事にも言及されているように、データの量が増えていくと関数よりJSONファイルを読み込む方法がいいようだ。
※参考:EJSでテンプレートとデータでファイルを切り分けた開発を行う | 大阪市天王寺区SOHOホームページ制作 | デザインサプライ-DesignSupply.-
JSONファイルの読み込みには「fs」モジュールを使う。以前Node.jsでファイルを表示した時にも使ったモジュール。
※参考:【Node.js】ルーティング設定でCSS、JSファイルを読み込む - クモのようにコツコツと
「gulpfile.js」で「fs」モジュールを読み込む
const fs = require('fs');
EJSコンパイル部にJSONの設定を追記
EJSコンパイル部分を修正
// EJSコンパイル gulp.task('ejs', (done) => { const data = JSON.parse(fs.readFileSync('./src/json/data.json')); gulp.src(["./src/ejs/**/*.ejs", "!./src/ejs/**/_*.ejs"]) .pipe(plumber()) .pipe(ejs(data)) .pipe(ejs({}, {}, { ext: ".html" })) .pipe(rename({ extname: ".html" })) .pipe(replace(/^[ \t]*\n/gmi, "")) .pipe(gulp.dest("./dest/")); done(); });
- 変数
data
でJSON.parse()
メソッドを実行。引数はfs
モジュールのreadFileSync()
メソッド readFileSync()
メソッドの引数は「data.json」のパス.pipe()
を追加。ejs()
メソッドでdata
を読み込む
JSON.parse()
メソッドはJSONデータをJSに変換する(ちなみに逆の変換はJSON.stringify()
)
※参考:REST APIとは何かを調べまくったらようやくイメージができてきたのでまとめた - クモのようにコツコツと
JSONファイルは拡張子が「.json」になるので「ejs」フォルダ内ではなく「src」フォルダ直下に「json」ファイルを作ってその中に置こうと思う。
そのため、パスを./src/json/data.json
にした。
watchの監視にJSONファイルを追加
jsonファイルの修正もwatch()
メソッドで監視したい。
// 監視ファイル gulp.task('watch-files', (done) => { gulp.watch(["./src/ejs/**/*.ejs", "./src/json/**/*.json"], gulp.task('ejs')); gulp.watch("./dest/*.html", gulp.task('browser-reload')); gulp.watch("./src/scss/**/*.scss", gulp.task('sass')); gulp.watch("./dest/css/*.css", gulp.task('browser-reload')); gulp.watch("./src/ts/*.ts", gulp.task('ts')); gulp.watch("./dest/js/*.js", gulp.task('browser-reload')); done(); });
- ejsファイルを監視してた
watch()
の第1引数を配列にしてjson
ファイルも監視対象に追加
これでJSONファイルを修正するとejsタスクでコンパイルされて、ブラウザも自動リロードするはず。
JSONデータを作成
まず先ほどの「contents」フォルダと「_contents.ejs」は削除する。
「ejs」フォルダの中ではなく「src」フォルダ直下に「json」フォルダを作成。その中に「data.json」を作る。
「data.json」の中身
{ "data": { "header": { "title":"クモのようにコツコツと ~別館~", "text": "イイダリョウのメタ言語実験室" }, "main": { "title":"メタ言語同時コンパイル(EJS、Sass(SCSS)、TypeScript)", "lede": "CodepenのSettingの旅CSS編第2回。今回はSCSS。LESSに似てる?", "link": "別ページへ" }, "inner": [ { "title":"ネスト(階層化)", "text": "SCSSもdivのネストの階層化ができる。この段落は<em class='inner__text-em'>ネスト</em>でスタイルを当てている。<br>ん?この書き方はLESSとほぼまったく同じだ。<br><span class='inner__text--bikou'>インデントについても厳密でじゃでなくても解釈してくれるようだ(?)</span>" }, { "title":"親セレクタの参照", "text": "おそらくLESSにはないSCSS専用の機能。ネストして「&」に擬似要素を付けると親セレクタを参照できる<br><span class='inner__text-bikou'>この尾行欄につけている「※」印は「.bikou:before」ではなく「&:before」だけで済んでいる。 </span>" }, { "title":"TypeScriptで書いたクリックイベント", "text": "<button class='inner__text-btn'>ここ押せワンワン</button>" } ], "footer": { "text":"© 2020 イイダリョウ" } } }
- 全体を
data
キーでheader
、main
、inner
、footer
キーを包んでいる - シングルコーテーションは全てダブルコーテーションにする
- キー名も全てダブルコーテーションを付ける
- タグの中の属性値はシングルコーテーションにする(重複しないように)
JSONデータはダブルコーテーションがメインの世界。
index.ejsでJSONデータをリンク(コンテンツ部分は削除)
インクルード部分を修正
index.ejsでインクルード部分を修正
<!doctype html> <html> <head> <%- include('base/_head', {main: data.main}); %> </head> <body> <%- include('block/_header', {header: data.header}); %> <%- include('block/_main', {main: data.main, inner: data.inner}); %> <%- include('block/_footer', {footer: data.footer}); %> </body> </html>
- 冒頭にあった「contents.ejs」のインクルードは削除
include()
で渡す変数はdata.main
などのようにdata
オブジェクトのプロパティとして読み込む
index.ejsすごくシンプルになった!
ejsファイル上ではJSONファイルのインクルードの記述は不要で、JSONデータの中に書かれているキー名を追記するだけでOK!
自分はこの方法が断然いい!
動作確認
「gulpfile.js」の変更を反映するため、いったんGulpを停止する。
「Control」+「C」を押すとターミナルにこの記述が出る。
^C
gulp再起動!
npx gulp
ブラウザが立ち上がる やた!JSONデータのテキストが読み込まれて表示されとる!
JSONデータを打ち替えてみる
このテキストが本当にJSONデータからのテキストなのかを確認したい。
headerの修正
header
のtext
に「!」を5つ追加してみると…
"header": { "title":"クモのようにコツコツと 〜別館〜", "text": "イイダリョウのメタ言語実験室!!!!!" },
おお、ヘッダーのテキストに反映された!!!!!
mainの修正
次にmain
のlede
のテキストも打ち替えてみる
"main": { "title":"メタ言語同時コンパイル(EJS、Sass(SCSS)、TypeScript)", "lede": "HTMLテンプレートエンジンとAltCSSとALtJSを同時にコンパイルする!!", "link": "別ページへ" },
h1タグの下のリード文が打ち変わった!
innerの修正
最後にinner
に段落(title
とtext
のセット)を追加してみる
"inner": [ { "title":"JSONデータの読み込み", "text": "コンテンツ部分を外部のJSONデータから読み込むことができる。<br>gulpfile.jsでfsモジュールを使って設定している。" }, // 中略 ],
本文に段落が追加された!楽ちん!!!
最後に:データとテンプレートの分離
今回、コンテンツ部分を以下の方法を試みました
- モジュール化→変数が読み込まれず
- モジュールの関数化→変数が読み込まれず
- JSONファイル化→変数が読み込まれた!
JSONファイルを読み込むにはgulpfileでfs
モジュールを使った処理を設定する必要がありました。
それによってデータとテンプレートの分離がより明確になり、結果的には一番いい方法になったと思います!
JSONデータを読み込むということは、今回のような自作JSONファイルだけでなくデータベースとの連携などもできそうです。
次回からはTypeScriptのモジュール化を体験してみたく思います。それではまた!
※参考:【メタ言語】HTMLテンプレートエンジン、AltCSS、AltJSのまとめ
qiita.com