What is wig?

wigは、素早く確実にWebページを更新し続けるために作られた、静的サイト/ドキュメントジェネレータです。
JSONなどで定義されたデータと、テンプレートを結合し、静的ページを出力します。

data

json, js, Markdown, yaml, text etc.

+

template

nunjucks template

wig build / wig.build()

static web site/document

Concept

wigのコンセプトは「 DRY(Don't Repeat Yourself)」です。

wigは元々、多言語展開された中規模Webサイトを、長く更新/メンテナンスし続けていくために作られたツールです。 たくさんのページを確実に更新・管理していくために、

ということを実現する必要がありました。そのために

を組み合わせて作られた静的サイト/ドキュメントジェネレータが、wigです。

Getting started

1.ディレクトリの準備

プロジェクトのルートディレクトリに、3つのディレクトリを準備します。

/data ・・ データを格納します
/templates ・・ テンプレートを格納します
/public ・・ ビルドした静的ファイルが出力されます

※ 各ディレクトリパスはオプションで自由に設定できます

2. データとテンプレートの作成

Setup dataTemplatingを参考に、データとテンプレートを用意します。
https://github.com/zk33/wig/tree/master/sample にいくつかのサンプルがありますので参考にしてください。

3.ビルドする

3-1.コマンドラインでビルドする

wigをインストールします

npm install -g wig

ビルドコマンドを走らせます。

wig build

3-2.タスクランナーでビルドする

wigをインストールします

npm install wig gulp gulp-watch

gulpfile.jsにタスクを用意します。

var path = require('path');

var gulp = require('gulp');
var watch = require('gulp-watch');

var Wig = require('wig');

var builder;
var opt = {
  rootDir:__dirname, // プロジェクトのrootディレクトリ指定
  dataDir:"./data", // dataを格納するディレクトリ
  publicDir:"./public", // 出力(=公開)ディレクトリ
  tmplDir:"./templates", // テンプレートを格納するディレクトリ
  verbose: true // ファイル出力時に出力したファイル名と使ったテンプレート名を表示します
}

gulp.task('wig', function() {
  if (!builder) {
    builder = new Wig(opt);
  }
  try {
    builder.build();
  } catch (e) {
    console.log(e);
  }
});

var watchSrc = [
  path.join(__dirname, opt.dataDir, '**', '*'),
  path.join(__dirname, opt.tmplDir, '**', '*')
]

gulp.task('wig:watch', function() {
  watch(watchSrc, function() {
    gulp.start('actless:wig');
  });
});

gulp.task('default',['wig','wig:watch']);

gulpを走らせます

gulp

※ SassやJSのコンパイルなどを含めたセットアップは、 actlessを使うと便利です。
ちなみにこのドキュメントもwig(というかactless)を使って作られていますので、 参考にしてください

Setup data

wigでは、dataディレクトリに格納したファイルの内容を元にして、ページが出力されます。

dataディレクトリ内のデータには、2つの役割があります。

  1. どの階層に、どういう名前でページを出力するかを指定する
  2. 各ページで使う変数を定義する

1. 出力するページを指定する

dataディテクトリ内に置いたファイル(と、その中身)によって、どのような名前のファイルをどの階層に出力するかを設定することができます。
出力するページの指定には、2つの方法があります。

1-1. 出力したいファイルと同名のファイルを置く

dataディレクトリ内に、/index.json/index.js/index.md、などのファイルを置いてビルドすると、publicディレクトリに、/index.htmlが出力されます。

dataディレクトリに置いたファイルの名前/階層と、出力されるファイルの名前/階層は同じになります。

'data'に置くファイル publicに出力されるファイル
/index.js /index.html
/foo/bar.md /foo/bar.html
/foo/bar/baz.json /foo/bar/baz.html

※ JSONやJS、YAMLファイルの場合は、ファイル内に書かれたデータ内容が、変数としてテンプレートに渡されます。

※ ページとして出力したい場合、ファイル名を_(アンダースコア)から始めることはできません。_から始まったファイルは、変数として処理されます。

1-2._contents変数を使う

dataディレクトリ内に置いたJSONファイル(またはJSファイル)で、_contentsという変数を設定すると、それに基づいてページが出力されます。

その際、_contentsが定義されているファイルの名前がディレクトリ名になります。

_contentsの中身を、ファイルを置いたディレクトリと同じ階層に出力したい場合は、__init__.jsonというファイル内に_contentsを指定してください。

_contentsは入れ子にできるので、1ファイルでサイトの全てのページを定義することもできます。

たとえば以下の内容を、

{
  "_contents":{
    "index":{
      "title":"indexページ"
    },
    "profile":{
      "title":"プロフィール"
    },
    "works":{
      "_contents":{
        "work1":{
          "title":"作品1"
        },
        "work2":{
          "title":"作品2"
        }
      }
    }
    "contact":{
      "title":"お問い合わせ"
    }
  }
}

dataディレクトリの/hello.jsonに書いた場合は、publicディレクトリには

/hello/index.html
/hello/profile.html
/hello/works/work1.html
/hello/works/work2.html
/hello/contact.html

が出力されます。
/__init__.jsonに書いた場合は

/index.html
/profile.html
/works/work1.html
/works/work2.html
/contact.html

が出力されます。

※ 出力されるファイルの拡張子は、「_ext」という変数(後述)で指定することができます。

2.変数の定義

dataディレクトリのファイルでは、ページの定義だけでなく、テンプレートに渡す変数を指定することができます。

ページごとにタイトルやdescriptionを設定したり、テンプレート上でページの処理を分岐させるためにIDを渡すなど、様々に活用できます。

2-1. ファイルで定義する

dataディレクトリ内に置かれたファイルのうち、ファイル名がアンダースコア(_)から始まるものは、変数として扱われます。

たとえば、data/_hello.txtの中身は、「hello」という名前でテンプレートに渡されます。

アンダースコア(_)を最初につけたファイルが、JSONなどの複数変数を設定できるファイルの場合、ファイル名を変数名としたObjectになります。

たとえば、data/_foo.jsonの中身が

{
  "bar":"baz"
}

の場合、テンプレート上では

{{ foo.bar }}

で表示させることができます。

2-2. ページ定義と一緒に変数を指定する

ページ定義で配置したファイルに、一緒に変数を定義することができます。
たとえば、以下のようにページと変数をまとめて設定できます。

{
  "IMG_DIR":"/assets/img",
  "title":"my cool website",
  "_contents":{
    "index":{
      "title":"top"
    }
    "profile":{
      "title":"my profile",
      "foo":"bar"
    }
  }
}

ディレクトリ構造と変数の関係

変数は、ディレクトリごと、もしくはページごとに指定できます。
ディレクトリ単位で設定された変数は、 そのディレクトリと、その子階層のファイルの出力時にテンプレートに渡されます。

たとえば、dataディレクトリに

/index.json
/_foo.txt
/bar/index.json
/bar/_bar.txt

というふうにファイルを置くと、publicディレクトリには

/index.html
/bar/index.html

が出力されます。この時、

親階層で定義した変数は全て子階層で使える ので、たとえばルートディレクトリ内に置いた__init__.jsonで設定した変数は、グローバル変数として全てのページで参照できます。

さらに、子階層やページで同じ名前で変数を設定すると、親階層の変数定義をオーバーライドすることができるので、

{
  "title":"my cool website",
  "_contents":{
    "index":{}
    "profile":{
      "title":"my profile"
    },
    "works":{
      "title":"works",
      "_contents":{
        "work1":{},
        "work2":{}
      }
    }
  }
}

このようにして、ページ別にtitleを設定している場合はそれを、設定がなければデフォルトのtitleを表示させるような事も可能です。

対応ファイル形式

dataディレクトリに置くファイルとして、対応しているファイルの形式は、

です。それ以外の形式のファイルも使えますが、全てテキストファイルとして処理されます。

Markdown(.md)ファイルの場合

Markdown(.md)ファイルの場合に限り、テンプレート上で参照する際の変数名が変わります。

dataディレクトリ内の

/index.md

に書かれたMarkdownをテンプレートで表示する際は

{{ _html | safe }}

でパースされたHTMLが出力されます。もしパースされてない状態のMarkdownテキストをそのまま出力したい時は{{_raw}}としてください。

もし変数として

/_markdown.md

dataディレクトリに置いた場合は、

{{ markdown._html | safe }}
{{ markdown._raw }}

とテンプレート上で書くことで出力できます。

また、Markdownファイルの先頭に、YAMLで変数を埋め込んでおくことができます。

<!--
title: Hello
updated: 2018-05-01
-->

# hello,world!

my awesome markdown text

という風に書いておくと、テンプレート上では上述の_html_rawに加えて、titleupdatedといった変数をテンプレート上で参照できるようになります。
この時、必ずファイルの1番先頭に<!--があるようにしてください。

__init__ファイル

変数/ページ定義ファイルのうち、__init__.json__init__.jsなど、__init__という名前のファイルは、少し特別な扱いがされます。

定義済みの特殊変数

基本的に、dataディレクトリ内で定義する変数名は、自由に設定して大丈夫なのですが、_contents変数のように、デフォルトの挙動を変更するための特殊な変数がいくつか定義されています。

_ext

ファイルを出力する時に、拡張子を指定したものに変更することができます。 たとえばサイト全体をPHPとして出力したい場合は、dataディレクトリの/__init__.json

{
  "_ext":"php"
}

と書くことで、全てのファイルの拡張子を.phpに変更することができます。
ページ/ディレクトリ単位で変更したい場合は、

{
  "_contents":{
    "index":{
      "_ext":"php"
    }
    "profile":{},
    "works":{
      "_ext":"php",
      "_contents":{
        "work1":{},
        "work2":{},
        "work3":{}
      }
    }
  }
}

というように必要なページやディレクトリ単位で_ext変数を設定してください。

_template

テンプレートのファイル名を指定します。
wigでは、ファイル出力時にどのテンプレートを使うかはルールに基づいて自動で決定されます(後述)が、そのルール外のテンプレートを使いたい場合に指定してください。

こちらも_extと同様、ディレクトリやファイル単位で指定できます。

{
  "_contents":{
    "index":{
      "_template":"foo/bar/template.html"
    }
    "profile":{},
    "works":{
      "_template":"foo/bar/for_works.html",
      "_contents":{
        "work1":{},
        "work2":{},
        "work3":{}
      }
    }
  }
}

Templating

wigは、テンプレートエンジンとして nunjucksを採用しています。 nunjucksは、 jinja2をベースにしたテンプレートエンジンで、継承やマクロ、ループ構造など、DRYにページを構築する上で役立つ様々な仕組みを持っています。

{% extends "__base__.html" %}

{% block header %}
<h1>{{ title }}</h1>
{% endblock %}

{% block content %}
<ul>
  {% for item in items %}
  <li>{{ item.name }}</li>
  {% endfor %}
</ul>
{% endblock %}

テンプレートの詳細は、 nunjucksのドキュメントをご覧ください。

出力時に使用されるテンプレート

publicディレクトリにファイルを出力する時、templateフォルダ内のどのテンプレートを使ってレンダリングするかは、dataで決められた出力ファイル名を元にして、自動的に決まります。

テンプレートの自動決定のルール

  1. 出力ファイルと完全に同じディレクトリ/ファイル名のテンプレートを探します。この時、_extパラメータが指定されている場合は、指定された拡張子→.html.njkの順に拡張子を変えて探します
    /foo/bar.html -> /foo/bar.html
  2. ディレクトリの区切りをアンダースコアに置換したものを探します
    /foo/bar.html -> foo_bar.html
  3. ディレクトリ内に__base__.htmlがあれば、それを使います
    /foo/bar.html -> foo/__base__.html
  4. 1階層上に上がって、同じことを繰り返します。

たとえば、出力するファイルが、/spam/egg/ham.phpの場合、

/spam/egg/ham.php
/spam/egg/ham.html
/spam/egg/ham.njk
/spam_egg_ham.php
/spam_egg_ham.html
/spam_egg_ham.njk
/spam/egg/__base__.php
/spam/egg/__base__.html
/spam/egg/__base__.njk
/spam/egg.php
/spam/egg.html
/spam/egg.njk
/spam_egg.php
/spam_egg.html
/spam_egg.njk
/spam/__base__.php
/spam/__base__.html
/spam/__base__.njk
/spam.php
/spam.html
/spam.njk
/__base__.php
/__base__.html
/__base__.njk

これを上から順に探して、見つかったテンプレートを使います。

このルールから外れたテンプレートを使いたい場合は、dataで該当するファイル/ディレクトリのところに_template変数で指定してください。

自動生成されるテンプレート変数

dataディレクトリ内で定義された変数以外に、自動で生成されてテンプレートに渡される変数があります。

_rel_root

publicのルートディレクトリまでの相対パスを出力します。 サイト内のリンクを相対パスで書きたい場合などに便利です。

<img src="{{_rel_root}}/assets/img/logo.png" alt="" />

と書くことで、

<img src="../../assets/img/logo.png" alt="" />

のように、ページの階層に応じた相対パスでルートディレクトリを参照させることができます

JS API

constructor

wigを初期化します。

let Wig = require('wig');
let options = {}
let wigInstance = new Wig(options);

constructor options

let options = {
  rootDir: '', // 処理する際のrootになるディレクトリの指定
  dataDir: './data', // データ・変数のファイル格納用ディレクトリ
  tmplDir: './templates', // テンプレートディレクトリ
  publicDir: './public', // 出力先ディレクトリ
  verbose: true, // trueにすると、ファイル出力時に、出力パスなどの情報を出力します
  vars: {} // テンプレート向けのグローバル変数を初期化時に指定する
}

.build()

静的ファイルを出力します。

let Wig = require('wig');
let options = {}
let wigInstance = new Wig(options);

wigInstance.build();

.addRendererFilter(filterName,filterFunc)

nunjucksにフィルタを追加します。

let Wig = require('wig');
let options = {}
let wigInstance = new Wig(options);

wigInstance.addRendererFilter('date', function(date,postfix){
  var d = new Date(Date.parse(date));
  return d.toDateString() + postfix;
});

wigInstance.build();

Command line API

install

npm install -g wig

wig build

wig build

dataディレクトリとtemplateディレクトリの内容を元に、静的ファイル一式をビルドします。コマンドラインオプションは以下の通りです。

-d, --data_dir <path>    Data directory (default:./data)
-t, --tmpl_dir <path>    Template dirctory (default:./templates)
-p, --public_dir <path>  Output dirctory (default:./public)
-v, --verbose            Display rendered files names
-a, --assign <items>     Assign template vars (KEY,VALUE,KEY,VALUE...)(default:"")
-h, --help               output usage information