ビルドシステムによるテーマ作成とファイル構成

今回はGruntを使い始めた頃から始めた、テーマ作成の方法とそのファイル構成について触れます。ビルドシステムを使ううえで便宜上こうなった、という類いの内容なので、参考程度にご覧ください。投稿作成にあたり、気付いて改良した内容も含みます。

ビルドシステムとその他ツール

プロジェクトディレクトリの構成

WordPress本体以外をgit管理するために、下記のような構成にしています。Wordpress本体はルートディレクトリの変更をする前提です。

project-name/
    ├ _src/ // 作業ディレクトリ
    │   ├ _bower_components/ // bowerでインストールするライブラリの管理用
    │   ├ ai // Illustratorファイル管理用
    │   ├ doc // 資料管理用
    │   ├ fonts/ // Webフォント管理用
    │   ├ images/ // 画像管理用
    │   ├ jade/ // 静的サイトファイル作成用(Wordpressの場合は使わない)
    │   ├ js/ // javascript管理用
    │   ├ project.json // jade用
    │   ├ psd/ // Photoshopファイル管理用
    │   ├ sass/ // sass管理用
    │   └ wpjade/ // WordPressテンプレファイル作成用
    ├ .bowerrc // bowerでインストールするライブラリの管理ディレクトリ指定用
    ├ .editorconfig // エディタ設定用
    ├ .htaccess // WordPress用 htaccess
    ├ bower.json // bowerでインストールするライブラリ管理用
    ├ gulpfile.js // Gulpファイル
    ├ index.php // WordPress用 index.php
    ├ package.json // node_modules 管理用
    ├ settings.json // Gulp設定用
    └ wp/ // WordPress本体

wpjadeディレクトリ内

wpjade内のjadeファイルをphpファイルとしてコンパイルする前提で下記のような構成にしています。

wpjade/
    ├ assets/
    │   ├ functions.php
    │   ├ screenshot.png
    │   └ style.css
    ├ index.jade
    ├ single.jade
    ├ page.jade
    ├ 404.jade
    ├ その他、テンプレファイル群
    └ templates/
        ├ _fb_sdk.jade
        ├ _ga.jade
        ├ _layout.jade
        ├ _ogp.jade
        ├ _site_tree.jade
        └ php/
            ├ _mixin.jade
            └ phpを記述したファイル群

テーマ用ファイルの内容

Theme Checkで得た情報を元に編集した、下記ファイルについてです。

  • function.php
  • style.css
  • screenshot.png

assets内ファイルの記述

functions.php(関連する内容のみ)

テーマディレクトリ内のcss/、js/内のファイルなどを読ませるための記述を入れてます。あと、タイトルタグ出力用の設定などを。下記サイトを参照させていただきました。

// -----------------------------------------
// wp-head 削除項目
// -----------------------------------------
remove_action('wp_head', 'feed_links', 2);
remove_action('wp_head', 'feed_links_extra', 3);
remove_action('wp_head', 'rsd_link');
remove_action('wp_head', 'wlwmanifest_link');
remove_action('wp_head', 'index_rel_link');
remove_action('wp_head', 'parent_post_rel_link', 10, 0);
remove_action('wp_head', 'start_post_rel_link', 10, 0);
remove_action('wp_head', 'adjacent_posts_rel_link_wp_head', 10, 0 );
remove_action('wp_head', 'locale_stylesheet');
remove_action('wp_head', 'wp_print_styles', 8);
remove_action('wp_head', 'wp_generator');
remove_action('wp_head', 'wp_shortlink_wp_head');
remove_action('wp_head', 'rel_canonical');
// 絵文字関係削除
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('admin_print_scripts', 'print_emoji_detection_script');
remove_action('wp_print_styles', 'print_emoji_styles' );
remove_action('admin_print_styles', 'print_emoji_styles');
// Embed関係削除
remove_action('wp_head','rest_output_link_wp_head');
remove_action('wp_head','wp_oembed_add_discovery_links');
remove_action('wp_head','wp_oembed_add_host_js');
// headへの//s.w.orgのDNDプリフェッチ挿入停止
function remove_dns_prefetch( $hints, $relation_type ) {
if ( 'dns-prefetch' === $relation_type ) {
return array_diff( wp_dependencies_unique_hosts(), $hints );
}
return $hints;
}
add_filter( 'wp_resource_hints', 'remove_dns_prefetch', 10, 2 );
// -----------------------------------------
// 読み込みファイルの設定
// -----------------------------------------
if (!is_admin()) {
    // 登録の解除
    function deregister_script(){
        wp_deregister_script('jquery');
    }
    // 登録する項目
    function register_script(){
        // ファーストビュー用CSS
        wp_register_style('fv', false);
        // nullでバージョン除去、最後の引数がtrueだとフッターに出力される
        wp_register_script( 'html5shiv', get_stylesheet_directory_uri() . '/js/html5shiv.min.js', '', null, false);
        wp_register_script( 'css3mediaqueries', get_stylesheet_directory_uri() . '/js/css3-mediaqueries.js', '', null, false);
        wp_register_script( 'jquery-original', get_stylesheet_directory_uri() . '/js/jquery.min.js', '', null, true);
        wp_register_script( 'js_project', get_stylesheet_directory_uri() . '/js/project.min.js', array( 'jquery-original' ), null, true);
    }
    // 登録した外部ファイルを出力
    function add_script() {
        deregister_script();
        register_script();
        wp_enqueue_style('fv');
        wp_enqueue_script('html5shiv');
        wp_enqueue_script('css3mediaqueries');
        wp_enqueue_script('jquery-original');
        wp_enqueue_script('js_project');
        // IEの条件付きコメント
        wp_script_add_data('html5shiv', 'conditional', 'lt IE 9');
        wp_script_add_data('css3mediaqueries', 'conditional', 'lt IE 9');
    }
    add_action('wp_enqueue_scripts', 'add_script');
    // 外部JS(project.min.js)にWPのテンプレートタグを渡す
    function wp_to_js() {
        $tRoot = esc_url(get_template_directory_uri());
        wp_localize_script( 'js_project', 'theme', array('url' => $tRoot));
    }
    add_action('wp_enqueue_scripts', 'wp_to_js');
    // スクリプトにdefer追加
    function defer_enqueue_script( $tag ) {
        return str_replace( ' src', ' defer src', $tag );
    }
    add_filter( 'script_loader_tag', 'defer_enqueue_script', 10, 2 );
    // インラインCSSの追加
    function add_inline_css() {
        $inline_css = file_get_contents( get_stylesheet_directory_uri() . '/css/fv.css', true);
        wp_add_inline_style( 'fv', $inline_css );
    }
    add_action( 'wp_enqueue_scripts', 'add_inline_css' );
    // jsとcssのtype属性を除去
    function remove_type_attr($tag) {
        return preg_replace("/type=['\"]text\/(javascript|css)['\"]/", '', $tag);
    }
    add_filter('script_loader_tag', 'remove_type_attr');
    add_filter('style_loader_tag', 'remove_type_attr');
}
// -----------------------------------------
// タイトルタグの出力
// -----------------------------------------
// add_theme_support( 'title-tag' ); を使用する
function setup_theme() {
    add_theme_support( 'title-tag' );
}
add_action( 'after_setup_theme', 'setup_theme' );
// タイトルからキャッチフレーズを削除する
function remove_tagline($title) {
    if ( isset($title['tagline']) ) {
        unset( $title['tagline'] );
    }
    return $title;
}
add_filter( 'document_title_parts', 'remove_tagline' );
// セパレータを任意のものに変更する
function custom_title_separator($sep) {
    $sep = '|';
    return $sep;
}
add_filter( 'document_title_separator', 'custom_title_separator' );

CSSの非同期読み込み

レンダリングブロック回避用にloadCSS.jsonloadCSS.jsで外部CSSの読み込みを制御します。プロジェクト用CSS(project.css)は、ファーストビュー用と切り分けたのちに利用します。下記の順番で処理する場合の記述を記載しておきます。

PageSpeed InsightsだとCSSが二重に認識されたりしてレンダリングブロック扱いになるんですが、体感速度は上がります。

  1. ファーストビュー用CSSのインライン挿入(function.phpで処理済み)
  2. プロジェクトテーマ用CSS読み込み
  3. normalize.css > テーマ用CSS読み込み後の処理
  4. Google Fontsの読み込み > テーマ用CSS読み込み後の処理

project.min.js

$(function(){
    // インラインCSS(ファーストビュー用CSS)の上にCSSを順番に挿入 / xxxxxxxはタイムスタンプ
    var theme_css = loadCSS( theme.url + '/css/project.css?xxxxxxx', document.getElementById("fv-inline-css") );
    onloadCSS( theme_css, function() {
        (読み込み後の処理)
        var
        normalize = theme.url + '/css/normalize.css',
        gf = '//fonts.googleapis.com/css?family=Roboto';
        loadCSS( normalize, document.getElementById("fv-inline-css") );
        loadCSS( gf, document.getElementById("fv-inline-css") );
    }
});

Javascriptの非同期読み込み

(現在、保留中です。)

style.css

Tagsは入れても入れなくてもTheme Check で怒られるので入れていません。テキストドメインのエラーが未だわからず、という状況です。
下記の記事のおかげで、タグの指定内容が決まっていることがわかりました。Theme Tagsの内容を参照して指定しておけば良いようです。
WordPressテーマを自作する際のstyle.cssに記述するテーマ情報のテンプレート(子テーマ制作にも対応) | オレインデザイン

/*
Theme Name:  (テーマ名)
Theme URI:   (配布テーマではないので使用するWebサイトのドメインを入れてます。)
Description: (テーマの簡単な説明)
Version: 1.0
Author: Shinichi Kuroda
Author URI: http://www.studiobusstop.com/
License:     GNU General Public License v2 or later // WordPress公式に準じて(詳しくないです)。
License URI: http://www.gnu.org/licenses/gpl-2.0.html // WordPress公式に準じて。
Text Domain: (テーマディレのスラッグ名にとりあえず設定しています。)
Tags: (Theme Tags の内容を参照してコンマ区切りで指定)
*/

screenshot.png

WordPress推奨の880x660pxで作成します。すると、Theme Checkで怒られるので、1200x900pxで作成します。「screenshot」なので、本来はテーマのスクリーンショットを入れるべきなのですが、制作者クレジット代わりにロゴを入れることが多いです。

テンプレート

WordPressのテンプレートパーツ機能は使わず、jade(pug)の extends と include を使ってテンプレートを作成する前提として、ベースとなる_layout.jadeを作成します。

テンプレートに記述するWordpress独自関数

Theme Check の情報を元に下記の関数を設定します。

基本

アクションフック用

クラス付加用

js読み込み用

ファビコン

ファビコン指定はサイトアイコン機能に任せる前提です。

wpjade/templates/_layout.jade

block current
block single
block pghdr
block row
-var root = '<?php echo esc_url(home_url()); ?>'
-var tRoot = '<?php echo esc_url(get_template_directory_uri()); ?>'
doctype
html()
    head(prefix=( current === 'home' ? 'og: http://ogp.me/ns# website: http://ogp.me/ns/website#' : 'og: http://ogp.me/ns# article: http://ogp.me/ns/article#' ) )
        meta(charset!='<?php bloginfo("charset"); ?>')
        include _site_tree
        | <?php wp_head(); ?>
        include _ogp
        include ./php/canonical.php
        //- css
        link(rel='stylesheet', media='screen', href!='#{tRoot}/css/#{name}.css')
        //- コメント返信のスクリプト(停止)
        //- | <?php if ( is_singular() ) wp_enqueue_script( 'comment-reply' ); ?>
    body(<?php body_class(); ?>)
        #container
            header.m-hdr
                h1.logo
                // ... ヘッダー用の記述
            main.m-body
                -if( pghdr )
                    header.m-pg-hdr(class='#{current}')
                        block pghdrContent
                -if( row )
                    .m-cont.l-flex
                        article(<?php post_class('l-main', 'l-float-l'); ?>)
                            block content
                    .l-sub.l-float-r
                        block subcontent
                -else
                    article(<?php post_class('m-cont'); ?>)
                        block content
            footer.m-ftr
                // ... フッター用の記述
        | <?php wp_footer(); ?>
        block script

wpjade/templates/_ogp.jade

meta(property='og:title' content!='')
-if( current === 'home' )
        meta(property='og:type' content='website')
        meta(property='og:description' content='#{des}')
        meta(property='og:url' content!='#{root}/')
-else
        meta(property='og:type' content='article')
        | <?php $current_url = esc_url( home_url() . $_SERVER['REQUEST_URI'] ); ?>
        meta(property='og:url' content!='')
meta(property='og:image' content!='#{tRoot}/images/ogimg.png')
meta(property='og:site_name' content!="")

wpjade/templates/php/canonical.php(カノニカルの設定)

ページネーションのSEO ( rel=”canonical” / “next” / “prev” )に記載。

テンプレート本体の構成

extends ./templates/_layout
include ./templates/php/_mixin
block current
    -var current = 'news'
block pghdr
    -var pghdr = 1
block row
    -var row = 1
block single
    -var single = 1
block pghdrContent
    // 必要な記述
block content
    // 必要な記述
block subcontent
    // 必要な記述
block script
    script.
        window.addEventListener( 'load', function(){
            $(function(){
                // 必要な記述
            });
        });

最終的なテーマディレクトリ内

wp/
    └ wp-content/
        └ themes/
            └ project-name/
                ├ css/
                │    ├ fv.css
                │    ├ normalize.css
                │    └ project.css
                │
                ├ fonts/
                ├ images/
                ├ js/
                │    ├ jquery.min.js
                │    ├ jquery.min.map
                │    └ project.min.js / loadCSS、onloadCSSを含む
                │
                ├ languages/
                ├ functions.php
                ├ screenshot.png
                ├ style.css
                ├ index.php
                ├ single.php
                ├ page.php
                ├ 404.php
                └ その他、テンプレファイル群

さいごに

bowerは主にjQueryとnormalize-css用で、いいかげんnpmで一元管理したいと思いつつ、そのままな状況です。
以上です。