メタ言語の続きです。前回はSass(SCSS)の変数($
)や@mixin
を使ってモジュールを超えた値の受け渡しをしました。今回はEJSの方もBEMのブロックごとにモジュールファイルを分割してみたく。include()
メソッドを使います。それではいきましょう!
【目次】
- 前回までのおさらい(Sass(SCSS)のモジュール分割)
- 「ejs」フォルダを作ってejsファイルを移動
- gulpfile.jsのパスを書き換え
- include()メソッドで別ファイルをインクルード
- BEMのブロックごとにパーシャル化
- ヘッダーの追加
- mainブロックにmainタグを追加
- フッターの追加
- 今回変更したコード(全体)
- コンパイルされたindex.html
- おまけ:Sass(SCSS)にヘッダー、フッターのモジュールを追加
- 最後に
※参考:前回記事
【Sass(SCSS)】変数($)、@mixinを使ってモジュールを超えた共通スタイルを設定する - クモのようにコツコツと
※参考:【メタ言語】HTMLテンプレートエンジン、AltCSS、AltJSのまとめ
qiita.com
前回までのおさらい(Sass(SCSS)のモジュール分割)
「scss」フォルダのファイル構成
前々回、Sass(SCSS)の方は「block」フォルダでBEMのブロックごとにモジュールをファイル分割。@import
を使ってstyle.sccsでインポートした。
※参考:【Sass(SCSS)】@importでBEMのBlockごとにファイル分割する - クモのようにコツコツと
そして前回「setting」フォルダで変数や@mixin
の設定ファイルも作成した。
※参考:【Sass(SCSS)】変数($)、@mixinを使ってモジュールを超えた共通スタイルを設定する - クモのようにコツコツと
なお、ファイル名の頭に_
がついたファイルは「パーシャル」ファイルとしてコンパイルからは除外される。
今回はEJSの方も同じようにBEMのブロックごとにモジュールのファイル分割を行いたい。
「ejs」フォルダを作ってejsファイルを移動
これまでejsファイルはhtmlファイルと同様に「src」フォルダの直下に置いていた。
しかしこれからejsファイルをモジュール分割していくと多分収拾がつかなくなりそうな気がする。
そのため、「ejs」フォルダを作ってejsファイルをその中に移動してみた。
これからはこのフォルダでモジュール分割を行っていく。
gulpfile.jsのパスを書き換え
ejsタスクを修正
ejsファイルをejsフォルダに移動したことで「gulpfile.js」の中のejsコンパイル設定がリンク切れになると思ふ。パスを書き換えよう。
EJSコンパイル部分
// EJSコンパイル gulp.task('ejs', (done) => { gulp.src(["./src/ejs/**/*.ejs", "!./src/ejs/**/_*.ejs"]) .pipe(plumber()) .pipe(ejs({}, {}, { ext: ".html" })) .pipe(rename({ extname: ".html" })) .pipe(replace(/^[ \t]*\n/gmi, "")) .pipe(gulp.dest("./dest/")); done(); });
gulp.src()
の引数にあるパスに/ejs/**
を追加(2箇所)
ちなみに、このejsタスクを作っている当時はわかっていなかったのだが
gulp.src(["./src/ejs/**/*.ejs", "!./src/ejs/**/_*.ejs"])
このgulp.src()
の引数にある配列の2番目は頭に!
が付いている。!
は「○○以外」なのでコンパイルから除外するという意味だった。
これによってSass(SCSS)と同様にファイル名の頭に_
を入ったファイルがパーシャル(コンパイル除外)になる!
watchタスクを修正
watch
の監視ファイルも変更
// 監視ファイル gulp.task('watch-files', (done) => { gulp.watch("./src/ejs/**/*.ejs", 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(); });
gulp.watch()
のejsの部分に/ejs/**
を追加
Sass(SCSS)の時と同じく/**
で下層フォルダ全てを適用範囲にする。
パスの変更はあくまでコンパイル前のejsファイルのみのため、htmlの部分は変更しない。
gulp起動
動作確認してみよう。ターミナルでcd
コマンドでフォルダに移動。
cd /(パス)/gulp_frontend
gulp実行!
npx gulp
ブラウザが立ち上がる。 よし!cssファイルやjsファイルのリンクも問題ないようだ!
include()メソッドで別ファイルをインクルード
Sass(SCSS)、別ファイルを読み込む時に@import
を使った。
@import 'フォルダ/ファイル';
※参考:【Sass(SCSS)】@importでBEMのBlockごとにファイル分割する - クモのようにコツコツと
同じような機能がEJSにもあり、include()
メソッドを使う。
<%- include('フォルダ/ファイル') %>
第二引数で別ファイルの中にある変数の値を渡すこともできる。
<%- include('フォルダ/ファイル', {キー: 値}) %>
※参考:テンプレートエンジンEJSで使える便利な構文まとめ - Qiita
この@include()
メソッドを使ってBEMブロックごとにejsファイルをパーシャル化しよう。
BEMのブロックごとにパーシャル化
headタグをパーシャルファイルにしてみる
まずは「base」フォルダを作り「_head.ejs」を作成。ファイル名に_
をつけることでパーシャルファイルになる。
<meta charset="UTF-8"> <title><%= main.title %></title> <meta name="description" content="<%= main.lede %>"> <link rel="stylesheet" href="css/style.css"> <script src="js/script.js"></script>
中身はmeta
タグの中身をそのままペースト。
この部分はBEMブロックではなくページに表示されない設定部分のため「base」フォルダにした次第。
index.ejsのheadタグの中身をinclude()
メソッドにする
<head> <%- include('base/_head', {main: main}); %> </head>
include()
メソッドの第1引数は「base」フォルダの_head.ejs(拡張子は省略)include()
メソッドの第2引数でmain
キーに変数main
を渡す設定
わかりやすいようにキー名は変数main
と同じにしてみた。
mainブロックをパーシャルファイルにする
次に「block」フォルダを作成。その中に「_main.ejs」を作る。
<section id="scss" class="main"> <h1 class="main__title"><%- main.title %></h1> <p class="main__lede"><%- main.lede %></p> <% for (var i = 0; i < inner.length; i++) { %> <section id="nest" class="inner"> <h2 class="inner__title"><%- inner[i].title %></h2> <p class="inner__text"><%- inner[i].text %></p> </section> <% } %> <p class="main__text"><a href="other.html" class="main__text-link"><%= main.link %></a></p> </section>
mainブロック部分をそのままペースト。
index.ejsのmainブロックがあった部分にinclide()
メソッド
<body> <%- include('block/_main', {main: main, inner: inner}); %> </body>
include()
メソッドの第1引数は「block」フォルダの_main.ejs(拡張子は省略)include()
メソッドの第2引数でmain
キーに変数main
を、inner
キーに変数inner
を渡す設定
innerブロック部分をパーシャルファイルにする
「block」フォルダの中に「_inner.ejs」を作成
<% for (var i = 0; i < inner.length; i++) { %> <section id="nest" class="inner"> <h2 class="inner__title"><%- inner[i].title %></h2> <p class="inner__text"><%- inner[i].text %></p> </section> <% } %>
innerブロック部分をペースト
「_main.ejs」の中のinnerブロック部分をinclude()
メソッドに
<section id="scss" class="main"> <h1 class="main__title"><%- main.title %></h1> <p class="main__lede"><%- main.lede %></p> <%- include('_inner', {inner: inner}); %> <p class="main__text"><a href="other.html" class="main__text-link"><%= main.link %></a></p> </section>
include()
メソッドの第1引数は同一フォルダの_inner.ejs(拡張子は省略)include()
メソッドの第2引数でinner
キーに変数inner
を渡す設定
ブラウザ表示確認
この状態でブラウザを見ると…
よし、index.htmlに無事コンパイルさている!EJSでは初のモジュールインクルード成功♪〜
今度はモジュールの新規作成をしてみる。ヘッダーフッターを追加する。
ヘッダーの追加
_header.ejsを作成
「block」フォルダの中に「_header.ejs」を作成
<header class="header"> <h1 class="header__title"><%- header.title %></h1> <p class="header__text"><%- header.text %></p> </header>
header
タグのclass名はブロック名.header
にする.header
の中にエレメント.header__title
、.header__text
を入れる.header__title
にはheader
オブジェクトのtitle
プロパティを読み込む.header__text
にはheader
オブジェクトのtext
プロパティを読み込む
既存の.main
ブロックなどと同じBEMの構成。ちなみにheader
オブジェクトはまだないw これから作成する。
index.ejsでheaderモジュールをインポート
「index.ejs」でheaderモジュールをインポートする
<body> <%- include('block/_header', {header: header}); %> <%- include('block/_main', {main: main, inner: inner}); %> </body>
body
タグの中、mainブロックの上にheaderモジュールをインポートする
headerオブジェクトを作成
先程headerモジュールの中で読み込む設定にしたheaderオブジェクトを作成
<% let header = { title:'クモのようにコツコツと ~別館~', text: 'イイダリョウのメタ言語実験室' } // 中略 %>
- 変数
header
の値は連想配列 title
キーの値は文字列text
キーの値は文字列
この構成もmainモジュールなどと同じ
コンパイル確認
この状態でブラウザを確認すると… やた!ヘッダーが表示された!(CSSはまだ設定してないので左寄せだが)
index.htmlにもヘッダーがコンパイルされている。
<body> <header class="header"> <h1 class="header__title">クモのようにコツコツと ~別館~</h1> <p class="header__text">イイダリョウのメタ言語実験室</p> </header> <!-- 中略 --> </body>
インデントがおかしいのが少し気になるが。。
mainブロックにmainタグを追加
header、footerのタグとの見通しをよくするためmainブロック(_main.ejs)のタグ全体をmain
タグで囲った。
<main> <section id="scss" class="main"> <h1 class="main__title"><%- main.title %></h1> <p class="main__lede"><%- main.lede %></p> <%- include('_inner', {inner: inner}); %> <p class="main__text"><a href="other.html" class="main__text-link"><%= main.link %></a></p> </section> </main>
フッターの追加
_footer.ejsを作成
同じ方法でフッターも追加してみる。「block」フォルダの中に「_footer.ejs」を作成
<footer class="footer"> <p class="footer__text"><%- footer.text %></p> </footer>
footer
タグのclass名はブロック名.footer
にする.footer
の中にエレメント. footer__text
を入れる.footer__text
にはfooter
オブジェクトのtext
プロパティを読み込む
headerモジュールと同じ構成(タイトルはなし)
index.ejsでfooterモジュールをインポート
footerモジュールもインポートする
<body> <%- include('block/_header', {header: header}); %> <%- include('block/_main', {main: main, inner: inner}); %> <%- include('block/_footer', {footer: footer}); %> </body>
body
タグの中、mainブロックの下にfooterモジュールをインポートする
footerオブジェクトを作成
こちらもheaderオブジェクトと同じ
<% // 中略 let footer = { text:'© 2020 イイダリョウ' } %>
- 変数
footer
の値は連想配列 text
キーの値は文字列
コンパイル確認
この状態でブラウザを確認すると… やた!フッターも表示された!(CSS設定はないのでこちらも左寄せ)
index.htmlにもフッター部分がコンパイルされている!
<body> <!-- 中略 --> <footer class="footer"> <p class="footer__text">© 2020 イイダリョウ</p> </footer> </body>
やっぱりインデントはおかしい。。
今回変更したコード(全体)
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 イイダリョウ' } %>
冒頭のオブジェクトはページに表示するテキストなど。だんだんボリュームが増えてきているのでなんとかしたい。単純にinclude()
するだけだとこの中のキー名が他のモジュールに読み込まれなくなったため、次回に対策する。
index.ejs(タグ部分)
<!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>
後半はページタグの部分。こちらはモジュール化したおかげででかなりスッキリした!
_head.ejs
headタグで読み込まれるmetaタグなどのモジュール
<meta charset="UTF-8"> <title><%= main.title %></title> <meta name="description" content="<%= main.lede %>"> <link rel="stylesheet" href="css/style.css"> <script src="js/script.js"></script>
_header.ejs
ヘッダー部分のモジュール
<header class="header"> <h1 class="header__title"><%- header.title %></h1> <p class="header__text"><%- header.text %></p> </header>
_main.ejs
ページ本文の部分のモジュール。
<main> <section id="scss" class="main"> <h1 class="main__title"><%- main.title %></h1> <p class="main__lede"><%- main.lede %></p> <%- include('_inner', {inner: inner}); %> <p class="main__text"><a href="other.html" class="main__text-link"><%= main.link %></a></p> </section> </main>
中でincludeモジュールを読み込んでいる。
_include.ejs
mainモジュールに読み込まれているincludeモジュール
<% for (var i = 0; i < inner.length; i++) { %> <section id="nest" class="inner"> <h2 class="inner__title"><%- inner[i].title %></h2> <p class="inner__text"><%- inner[i].text %></p> </section> <% } %>
for文でオブジェクトの数量分ループしている
_footer.ejs
フッター部分のモジュール
<footer class="footer"> <p class="footer__text"><%- footer.text %></p> </footer>
ファイル構成
モジュール分割されたファイル構成
- baseフォルダの中にheadモジュール
- blockフォルダの中にheader、main、include、footerモジュール
コンパイルされたindex.html
コンパイルされるとこうなる
(other.htmlは手付かずだがindex.htmlにモジュールが統合される)
index.htmlのコード
<!doctype html> <html> <head> <meta charset="UTF-8"> <title>メタ言語同時コンパイル(EJS、Sass(SCSS)、TypeScript)</title> <meta name="description" content="CodepenのSettingの旅CSS編第2回。今回はSCSS。LESSに似てる?"> <link rel="stylesheet" href="css/style.css"> <script src="js/script.js"></script> </head> <body> <header class="header"> <h1 class="header__title">クモのようにコツコツと ~別館~</h1> <p class="header__text">イイダリョウのメタ言語実験室</p> </header> <main> <section id="scss" class="main"> <h1 class="main__title">メタ言語同時コンパイル(EJS、Sass(SCSS)、TypeScript)</h1> <p class="main__lede">CodepenのSettingの旅CSS編第2回。今回はSCSS。LESSに似てる?</p> <section id="nest" class="inner"> <h2 class="inner__title">ネスト(階層化)</h2> <p class="inner__text">SCSSもdivのネストの階層化ができる。この段落は<em class="inner__text-em">ネスト</em>でスタイルを当てている。<br>ん?この書き方はLESSとほぼまったく同じだ。<br><span class="inner__text--bikou">インデントについても厳密でじゃでなくても解釈してくれるようだ(?)</span></p> </section> <section id="nest" class="inner"> <h2 class="inner__title">親セレクタの参照</h2> <p class="inner__text">おそらくLESSにはないSCSS専用の機能。ネストして「&」に擬似要素を付けると親セレクタを参照できる<br><span class="inner__text-bikou">この尾行欄につけている「※」印は「.bikou:before」ではなく「&:before」だけで済んでいる。 </span></p> </section> <section id="nest" class="inner"> <h2 class="inner__title">TypeScriptで書いたクリックイベント</h2> <p class="inner__text"><button class="inner__text-btn">ここ押せワンワン</button></p> </section> <p class="main__text"><a href="other.html" class="main__text-link">別ページへ</a></p> </section> </main> <footer class="footer"> <p class="footer__text">© 2020 イイダリョウ</p> </footer> </body> </html>
おまけ:Sass(SCSS)にヘッダー、フッターのモジュールを追加
ヘッダー、フッターのブロックにCSSが当たってないので、Sass(SCSS)にもモジュールを追加する。
_mixin.scssにページ横幅設定のmixinを追加
「setting」フォルダの「_mixin.scss」にページの横幅のmixinを追加した(複数のモジュールで共通するため)
@mixin pageSize($width: 960px) { width: $width; margin: 0 auto; }
- この設定は「_main.scss」にあったページ横幅設定
width
は後から変えられるように引数$width
にしてみた(初期値は960px
)
pageSize
のmixinを「_main.scss」でインクルード
/* main */ .main { @include pageSize(); &__title { color: $text-color; font-size: 2em; } &__lede { font-size: 1em; } }
- ページ幅の設定があった部分を
@include
で差し替える(引数は変更しないので空欄)
ブラウザ表示を確認すると…
よし、ページ幅は保持されている。
header.scss、footer.scssを作成
「block」フォルダの中に「_header.scss」を作成
/* header */ .header { @include pageSize(); }
pageSize
のmixinをインクルード
同様に「_footer.scss」も作成
/* footer */ .footer { @include pageSize(); }
pageSize
のmixinをインクルード
ブラウザを見ると
よし!ヘッダーフッターにもページ幅を適用されたぞ
「src」フォルダにヘッダー用の画像を追加
もう少し細かくスタイルを設定しよう。
まず「dest」フォルダに「img」フォルダを作成し、画像を追加。
これはヘッダーに使う画像を追加(当ブログにヘッダーと同じ画像)
今回は画像はコンパイルを行わない*1ため、「src」フォルダ直置きでいいかと。
_header.scssを修正
ヘッダーで先程追加した画像を背景に使う。
/* header */ .header { @include pageSize(); padding: 80px 10px; background-image: url(../img/header_bg.jpeg); background-size: cover; text-align: center; &__title { margin: 0; color: $text-color_w; text-shadow: 0 0 20px #000; } }
その他、titleを当ブログと同じ白文字のシャドー付きにしたり。
_footer.scssを修正
フッターも上に線を表示したりテキストセンター寄せにしたりと微調整
/* footer */ .footer { @include pageSize(); padding: 20px 0; border-top: 1px solid #ccc; text-align: center; &__test { font-size: 0.8em; } }
ブラウザ表示確認
こんなんでました! ヘッダー画像が付いて少し寂しさが紛れたw
CSSコンパイル結果
style.cssにもヘッダー、フッターが追加されている
@charset "UTF-8"; /***** base *****/ body { margin: 0; padding: 0; font-family: sans-serif; color: #333; } body a { color: #C30D23; } /***** block *****/ /* header */ .header { width: 960px; margin: 0 auto; padding: 80px 10px; background-image: url(../img/header_bg.jpeg); background-size: cover; text-align: center; } .header__title { margin: 0; color: #fff; text-shadow: 0 0 20px #000; } /* main */ .main { width: 960px; margin: 0 auto; } .main__title { color: #333; font-size: 2em; } .main__lede { font-size: 1em; } /* inner */ .inner { margin: 0 0 40px; } .inner__title { padding: 10px; background: #eee; border-left: 2px solid #C30D23; font-size: 1.5em; line-height: 1.2; } .inner__text-em { font-weight: bold; background: #eee; padding: 5px; color: #C30D23; } .inner__text-bikou { font-size: 0.8em; } .inner__text-bikou:before { content: "※"; } .inner__text-flex { display: flex; flex-wrap: nowrap; justify-content: center; align-items: stretch; } .inner__text-btn { background: #C30D23; color: #fff; padding: 10px; } /* footer */ .footer { width: 960px; margin: 0 auto; padding: 20px 0; border-top: 1px solid #ccc; text-align: center; } .footer__test { font-size: 0.8em; }
最後に
ということでSass(SCSS)に続き、EJSのほうもBEMの構成でモジュール分割しました!
やってみるとだんだん慣れていくし、あとあとの修正時にどこを直せばいいのか直感的にわかりそうなのがいいです。(縦に長〜いファイルだと検索置換などで探す必要がありますかね。。)
次回は今回宿題になった、EJSの本文部分のオブジェクトを外部ファイルにして読み込みたく思います。それではまた!
※参考:【メタ言語】HTMLテンプレートエンジン、AltCSS、AltJSのまとめ
qiita.com
*1:gulp画像圧縮などもできるようだが