使いまわしを想定したWebアプリプロジェクトを考える


この記事は FUJITSU Advent Calendar 2017 part2 の13日目です。

最近困っていたこと

最近、デモ用のWebアプリを作る機会が増えてきて、以下のことに困っていました。

  1. アプリをシンプルに作ったつもりが、使いまわしを考慮してないので、別件を頼まれたときに、新規にアプリケーションプロジェクトを作る羽目になる。
    • そのため
      • アプリとしての体裁を整えるまでに時間がとられ、見た目含めクオリティが上がらない。
      • 作り方を思い出すまでに時間がかかる。
      • 新しいことを試したい欲求にかられる。
  2. 他の人が作ったときのクオリティにばらつきが出る。
  3. デモアプリなので、内部のコードは汚くていいという心理が働く。

それぞれ言い訳をする。

  1. 突発的に頼まれたりしたときに、後先考えずに作ってしまう。
    • 新規にプロジェクトを作って、ある程度アプリとしての体裁を整えるまでが時間がかかる。一番面倒。
    • 前に作ったのをコピペしただけのはずなのに動かないことがある。
  2. 製品ならば、引継ぎ資料などを作りますが、デモ用なので、個人技でホイっと作ってしまう。
  3. 自家製の汚いソースを作ってしまい、後で誰かに渡すのが申し訳ないと考えてしまう。

改善したいポイント

  • 毎回、レベル1からのスタートはつらい。せめて、"強くてニューゲーム"を
    ⇒ プロジェクトをcloneし、コマンドを実行(npm installとgulp)するだけで、最低限の動きは保証。作り込みたい場所、差し替えればよい場所を誘導するアプリケーションプロジェクトを布教する。
    • 同時に以下にも対応。
      • 新しいことを試したい欲求。
      • 汚いコードの除去。

+α(ちょっとした願望) 新人にも少しずつ触れてもらい、Webアプリに慣れていってほしい。

// 残りの課題について。

  • 人間なので、忘れるのは仕方がない。

以上から、自分なりに使いまわしを想定したプロジェクトを考えてみた。

準備

  • Node.js・・・stableで良い。
    • gulpとbrowser-syncはインストール済みであること
$ npm install -g gulp browser-sync

npm install 前に、Proxy設定を確認。参考

技術選定

モバイル利用も想定して、SPAにすること前提で選定します。
ターゲットは、普段Webアプリを書いてない人。

  • フレームワーク
    • Riot.js
      • 今回、v3.7.4を利用
      • SPAの設定が簡単
      • タグベースのコンポーネント指向
      • scoped cssがある
      • ファイルが軽い
      • ドキュメントが日本語対応してる
  • HTMLテンプレートエンジン
    • pug
      • ファイル分割と、HTML/CSS/JSをincludeできる。
      • 記述量が減らせる。
        • もし、Pugが覚えられず、htmlで書いても許される。(しつこいですが、includeだけは使ってください。ごちゃまぜでも大丈夫)
      • ゴリラでもわかる
  • タスクランナー
    • gulp
      • pugの実行や集約を任せる
      • 各Alt系のビルド(必要なら)

テスト関連の話は今回触れません。

ビルドタスクの全体イメージ

画像のように、PostCSSやTypeScriptなど、Alt系をビルドしてから、Pugのincludeを利用し、CSSとJavaScriptを取り込んだHTMLを生成します。(Alt系を使わない場合は、真ん中の列(Pug+CSS+JS)だけやればよいです。今回、サンプルで紹介するソースにはpostcss/typescriptは出てきません。)

pugをビルドすると、デフォルトで拡張子がhtmlになります。拡張子を変えたい場合は各自変更してください。筆者は.tagに変更し、dist/tag/にコピーしてます。

プロジェクト構成

以下のようにしてみました。

<project_root>/
      ┣━assets/        ライブラリや変更を加えないファイルを格納
      ┣━dist/          ビルド後の資産格納
      ┃  ┣━css/
      ┃  ┣━tag/        pugをビルドした後のファイルを格納
      ┃  ┣━js/
      ┃  ┗━index.html
      ┣━node_modules/  使用するnode_moduleを格納
      ┣━src/           変更を加えるファイルを格納
      ┃  ┣━css/        postcsファイルの格納
      ┃  ┣━pug/        pugファイルのみ格納
      ┃  ┣━ts/         TS or JSファイルの格納
      ┃  ┗━index.html  開発序盤のライブラリの入れ替えを想定。src直下に置く。
      ┣━tmp            postcss/tsのビルド後の資産を一時格納
      ┃  ┣━ css/       ビルド後のCSS一時格納
      ┃  ┗━ js/        ビルド後のJS一時格納
      ┣━ .gitignore
      ┣━ gulpfile.js
      ┗━ package.json

pugのビルド後、ビルド後資産格納領域dist/に格納、この時、assets/に入れた、ライブラリもコピー・格納します。

格納後、サーバーを起動、テスト環境なら、browser-syncを使いましょう。
ライブリロードを活用した開発はオススメです。参考URL:Browsersyncを利用してお手軽ブラウザ確認環境をつくろう


//browser-syncの起動
gulp.task('setupBrowserSync', function () {
    browserSync({
        server: {
            baseDir: "./dist/"
        }
    });
});

// src/以下の資産の変更を検知すると、ビルドして、参照しているブラウザをリロードします。

gulp.task('watch', ['prepareBuild','setupBrowserSync'], function (cb) {
    gulp.watch(["./src/**"], function () {
        return runSequence(
      //      'build_PostCss', //PostCSSのビルド
      //     'build_TS',      // TypeScriptのビルド
            'build_Pug',      // Pugのビルド
            'copyExceptionFiles',     // 例外的なファイルのコピー(index.htmlなど)
            'reload'     // browser-syncの強制更新
        );
    });
});

gulp.task('prepareBuild', function (cb) {
  // 省略: watchのrunSequenceと同じ順でタスクを実行(reloadは不要)
});

タスクの実行順が重要です。run-sequenceを使用し、実行順を制御してください。

実装側

index.html

headerタグはheadernav.tagを固定参照、mainタグは各パスの情報を見て切り替わります。
具体的には、ルート以下が//#default のとき、default.tagを使用、
/#contentのとき、content.tagを使用します。

<!DOCTYPE html>
<head>
  <meta charset="UTF-8">
  <title>sample</title>

</head>
<body>
  <header></header>
  <main></main>

<!-- 利用するtagファイルを以下のようにして、取り込みます。 -->
  <script type="riot/tag" src="tags/headernav.tag"></script>
  <script type="riot/tag" src="tags/content.tag"></script>
  <script type="riot/tag" src="tags/default.tag"></script>

  <script src="assets/js/libs/riot+compiler.min.js"></script>
  <script src="assets/js/libs/route.min.js"></script>

  <script>
    // SPAのルーティングの設定。ヘッダー部
    route(function (tagName) {
      riot.mount('header', 'headernav');
    });
    // SPAのルーティングの設定。メイン部
    route(function (tagName) {
      tagName = tagName || 'default';
      riot.mount('main', tagName);
    });
    route.start(true);
  </script>
</body>
</html>

default.pug

defaultページのpugファイルのサンプルです。今回、css、jsの参照先は、default.pugから相対座標で指定してます。

default
  style
    include ../../tmp/css/default.css
  h1.test
  a(href="#content") gotoContentPage
  .default_container
    button(onclick="{sayHello}") Hello
  script
    include ../../tmp/js/default.js

css、jsのinclude先を変えるだけで、過去の負の遺産を封印したり、新しいことを試したときに、実験コードに入れ替えができます。

これだけで、新しいことを試したい欲求。汚いコードの除去の両方達成

webpackだと、loaderに引っかかったり、ファイルの退避が必要になってしまうこともあります。


content.pug

defaultページにあるリンクの遷移先のファイルです。
content.csscontent.jsは省略します。

content
  style
    include ../../tmp/css/content.css
  h1.test
  a(href="#") goBackDefaultPage
    include ../../tmp/js/content.js

default.css

dafault.pugで使用するcss

.test{
  color:red;
}
.default_container{
  width:100%;
  padding-top:25px;
}
.default_container>button{
  width:200px;
  height:40px;
}

default.js

default.pugで使用するJS

// this. で書くのはriotのお作法。
this.sayHello=function(){
  alert("Hello");
}

おわりに

Pugの使い方を見て、webpackで良くないか?ReactやAngular、Vueで良いのでは?と思う人もいると思います。筆者もこれを考え始めた当初、その線で考えていました。
しかし、今回、ターゲットとしたのが、普段Webアプリをやってない人ということで、デバッグさせるとき、webpack使わせるのも酷なのと(ソースマップがあればよいが)、筆者がgulpならまだマシ(人間が把握しきれる)という主観の元、この構成にしました。

別の狙いとして、browser-syncを使ったWebアプリの開発速度に毒したかったというのがあります。
riotについては、今回のパターンに都合がよかったのと、単に使ってみたい欲にかられました。

反省点
  • 実はけっこうガチなのを作ってありますが、自部署の製品向けに作りこみ過ぎてしまって、この記事を書くときに、プロジェクトを作り直しました。
  • もっとぶっ飛んだ記事を書きたい。JavaScriptで~~をやってみたとか。(それやる意味あった?と突っ込みがあるぐらいの)