UUUM攻殻機動隊(エンジニアブログ)

UUUMのエンジニアによる技術ブログです

静的サイトを制作する開発環境を作りました

こんにちは!
Hipster フロントエンドエンジニア ごーです。

弊社では、ランディングページなどの単発で必要になるサイトを実装することがよくあります。

そういった案件の場合は、各開発者がローカルに開発環境を持ち画面実装を行うか、
制作会社様へマークアップとスタイルを発注して納品して頂く流れになります。

しかし、属人性を少なくしていきたいと常々考えるところがありました。

本件では、Web制作環境を誰でも短時間で整えられる仕組みを構築した件についてご報告させていただきます。

また、本件の内容で扱うリソースは、こちら からダウンロードできます。

静的サイトとは

サーバーサイドでは特に処理を行わず、ブラウザからのリクエストに対して、そのままファイルをレスポンスするサイトのことです。

誰が静的サイトをつくるのか

マークアップエンジニアや、Web制作会社のような発注先の作業者が静的サイトを実装する想定です。

実装者は、ローカルファイルに対してマークアップやスタイルを実装していくと、ウェブサイトが出来上がることを期待しているものとします。

システムの要件

1ディレクトリで完結

ビルド対象のソースファイルは、src ディレクトリ、 ビルド後のリソースは public ディレクトリにあって、それぞれ1ディレクトリで完結しています。

マルチブラウザ対応

css や、 JavaScriptのマルチブラウザ対応は、publicビルド時に自動補完されている。

簡易ビルド

デバッグ時は、 src ディレクトリのリソースを参照している。

BrowserSync

デバッグ時は、頻繁にコードを変更するのBrowserSyncを使って、ソースコード変更を検知してブラウザがオートリロードする。

inline展開

サイト固有のcss、JavaScriptは、htmlにインライン展開されている。

SourceMap

デバッグ時のcssは、SourceMapに対応してしている。

デバッグビルド

ビルド待ちが開発の負担になる。

デバッグ時のcss, JavaScriptはマルチブラウザ対応のビルドは行わない。

lintチェック

publicビルド時にlintチェックを行う。

  • stylelint の後に、 prettier の処理を行う。
  • eslint の後に、 prettier の処理を行う。

シンプルなコーディング環境を目指しました

開発環境に込めたメッセージ

  • 開発プラットフォームやエディタ、さらに言語さえも実装者にとっては、押し付けられるものでしかなく、ビルド環境を構築した側と、ウェブ製作者の理想は必ずしも一致しないと仮説を立てました。

  • 実装や設計に関しては、ただひたすらウェブ制作者に移譲するほうが良い。

  • しかし、ソースコードレベルでは、ブレを少なくする様に適度に標準化されたコードを求めるようにします。

モジュールバンドラーは使わない

  • モジュール化されたJavaScriptをバンドルするようなシステムは、ソースコードと画面を対応させるのが難しいと思う方がいるかもしれません。

  • 作業を行える人材をスケールさせるために、求められる技術水準が高度なモジュールバンドラーは採用しません。

テンプレートエンジンは使わない

  • 標準なHTMLの方が、ネット上の情報など、そのまま使える事が多いです。Haml Slim Pug は、コードが短くなり便利ですが調べながらの開発だと、かえって負担になる場合があります。

  • HTMLは、エディタ側でコード補完が充実してきているので、マークアップの工数には影響が少ないと考えています。

主役はHTMLファイル

  • ライブラリはCDNを使います。HTMLのhead要素を参照すれば、どんなライブラリが使われているか分かるようにします。

  • HTMLファイルを見ればどんな内容のサイトなのか分かるようにします。

構成ファイル

ディレクトリ構造

/  
├ build  
│ └ inline.js  
├ public  
│ └ img  
│   └ .gitkeep  
├ src  
│ ├ css  
│ │ └ .gitkeep  
│ ├ img  
│ │ └ .gitkeep  
│ ├ js  
│ │ ├ .gitkeep  
│ │ ├ index.js  
│ │ └ plugin.js  
│ ├ lib  
│ │ └ .gitkeep  
│ ├ scss  
│ │ ├ .gitkeep  
│ │ └ index.scss  
│ └ index.html  
├ .babelrc  
├ .eslintrc.json  
├ .gitignore  
├ .stylelintrc.js  
├ package.json  
└ postcss.config.js  

HTML

HTML5 Boilerplate

長期に渡ってメンテナンスされていて、信頼できるボイラープレートなので、デフォルトのテンプレートとして採用しました。

index.html

ここでは、 htmlファイルが index.html のひとつだけですが、自由にhtmlファイルを増やして、ページを増やせます。


コード

<!doctype html>
<html class="no-js" lang="ja">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <title></title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="manifest" href="site.webmanifest">
    <link rel="apple-touch-icon" href="icon.png">
    <!-- Place favicon.ico in the root directory -->
    <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap-reboot.min.css">
    <style>.hello-world{color:#00f}</style>
  </head>
  <body>
    <!--[if lte IE 9]>
      <p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="https://browsehappy.com/">upgrade your browser</a> to improve your experience and security.</p>  
    <![endif]-->  
    <!-- Add your site or application content here -->
    <p class="hello-world">Hello UUUM! This is HTML5 Boilerplate.</p>
    <script src="//cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js"></script>
    <script src="//code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
    <script>// Add your jQuery plugin implements here...</script>
    <script>// Add your javascript implements here.</script>
    <!-- Google Analytics: change UA-XXXXX-Y to be your site's ID. -->
    <script>
      window.ga = function () { ga.q.push(arguments) }; ga.q = []; ga.l = +new Date;
      ga('create', 'UA-XXXXX-Y', 'auto'); ga('send', 'pageview')
    </script>
    <script src="//www.google-analytics.com/analytics.js" async defer></script>
  </body>
</html>

reboot.css

Bootstrap で採用されているCSSのリセットです。

特徴しては、以下が挙げられます。

  • box-size: border-box; が全ての要素に設定
  • 単位に rem を採用し、シンプルで自然なベーススタイルが適用される
  • Bootstrap 内でメンテナンスされており、信頼性が高い

inline-source

<script> <link> のタグ属性に inline を含ませると、srchref で指定されたコードがインライン展開されます。

inline.js

HTMLファイル 中の inline 属性を処理するスクリプトです。


コード

const { inlineSource } = require("inline-source");
const fs = require("fs");
    
const dir_in = "./src/";
const dir_out = "./public/";
// ファイル名の一覧
const filenames = fs.readdirSync(dir_in);
    
var fileList = filenames.filter(function(file) {
  return /.*\.html$/.test(file); //絞り込み
});  
function setInline(target) {
  inlineSource(dir_in + target, {
    compress: false
  }).then(html => {
    fs.writeFileSync(dir_out + target, html);
  });  
}  
fileList.forEach(function(name) {
  setInline(name);
});  

このスクリプトを実行すると、以下のように記述されたタグは、コードがインライン展開されます。

JavaScript
<script inline src="./js/plugin.js"></script>  
css
<link rel="stylesheet" href="./css/index.css" inline>  

参考

JavaScript

HTML5 Boilerplateindex.html から、予め参照されているJavaScriptファイル名をそのまま採用しました。

plugin.js

jQueryプラグインを使ったスクリプトの実装を行います。

index.js

jQueryプラグインに依存しないスクリプトの実装を行います。

Scss

index.scss

個別のスタイル実装を行います。

コンパイルされて /src/css.css が生成されます。

各種画像

HTML, Scss, JavaScriptから参照する画像を配置します。各ファイルのパスは、 index.html がある階層を / として、絶対パスで指定します。

Altjsは使わない

コーダーの必要スキルを引き下げるために、JavaScript以外のスクリプトは要求しません。

npm-scripts

以下が、組み上げたnpm-scriptsです。

{  
    "inline": "node ./build/inline.js",  
    "build:img": "cpx \"./src/img/**/*\" ./public/img",  
    "build:html": "run-s babel sass postcss inline",  
    "build": "run-p build:*",  
    "postcss": "postcss src/css --dir src/css",  
    "sass": "node-sass -r src/scss  -o src/css --source-map-embed --output-style expanded",  
    "babel": "babel src/js --out-dir src/lib",  
    "watch:sass": "node-sass -r src/scss  -o src/css --source-map-embed --output-style expanded -w",  
    "watch:babel": "babel src/js --watch --out-dir src/lib",  
    "lint:sass": "stylelint 'src/scss/**/*.scss' --fix",  
    "lint:js": "eslint src/js/**/*.js --fix",  
    "lint": "run-p lint:*",  
    "sync": "browser-sync start -s './src' -w 'src/*.html' 'src/css/*.css' 'img/*' 'src/lib/*.js'",  
    "start": "run-p sync watch:*"
  }  
}  

単一責任原則

npm-scriptsで割り当てられるCLIタスクは、長いワンライナーを割り当てると保守が難しくなります。

単一の機能をとして一つ一つ追加していきます。

参考

ビルドタスクランナーは使わない

gulp , grunt などのタスクランナーは、使用せずに npm-scripts を組み合わせて複雑な処理を実現します。

npm-run-all パッケージを使うと組み合わせるのが容易になります。

npm-run-all

複数の npm-scripts をパラレルまたはシーケンシャルに実行するためのCLIツールです。   古くはタスクランナーが担っていた、ウェブ制作における煩雑な作業を npm-run-all を使うことによって、自動化する事ができます。

シーケンシャルな処理

task1が終了を待って、task2が処理されます。

e.g.. run-s task1 task2

パラレルな処理

task1とtask2が同時に実行されます。

e.g.. run-p task1 task2

参考

マルチブラウザ対応

マルチブラウザ対応は自動化して、実装者の関心事から外します。

Babel

Babel とは、新しい機能を使って書いた JavaScript のコードを、以前のバージョンの書き方に変換してくれるツールです。

今回は、Bavel v7.1.0 を採用しています。

@babel/preset-env

必要なときだけpolyfillを追加してくれる便利なプラグインです。

polyfillのために潜在的に必要なimportを書いたり、polyfill用のパッケージをインストールする手間を解決してくれます。

また、指定したブラウザやバージョンに無い機能のみpolyfillが導入されるので、不要なpolyfillはインストールされません。

ブラウザの指定方法は、Browserslist 準拠です。

@babel/polyfill

必要な polyfill が揃ったパッケージ。

@babel/preset-env によって、自動インストール・インポートされます。

インストール

$ npm i --save-dev @babel/core @babel/cli @babel/preset-env  
$ npm i --save @babel/polyfill  

.babelrc

babelの設定ファイルです。

@babel/preset-env をbabelに組み込んだり、対応するブラウザの指定を設定します。

targets に指定する対応ブラウザバージョンの書き方は、Browserslistのクエリと互換性があります。

クエリが表すブザラウザは、browserl.istでチェック出来ます。

{  
  "presets": [  
    [  
      "@babel/env",  
      {  
        "targets": "> 1%, not dead",  
        "useBuiltIns": "usage"  
      }  
    ]  
  ]  
}

参考

Autoprefixer

cssをマルチベンダー対応にするために、ベンダープレフィックスを自動付与するツールです。

sassがcssへコンパイルされた後に実行します。

Autoprefierは、PostCSSの中のモジュールです。

インストール

$ npm i —save-dev postcss-cli autoprefixer  

postcss.config.js

module.exports = {  
  plugins: [  
    require("autoprefixer")({  
      browsers: ["last 2 versions"],  
      grid: true  
    }),  
    require("cssnano")({  
      preset: ["default"]  
    })  
  ],  
  map: false  
};  

参考

一貫したコーディングスタイルの徹底

stylelint , ESLintで構文チェック、Prettierではコード整形を行います。

stylelint

Stylelint は、構文エラーや規約に沿ったコーディングがされているかを検証する強力なツールです。

Stylelintが scssシンタックスに対応出来るように、 stylelint-scss をインストールします。

また、規約に関しては、 stylelint-config-sass-guidelines を採用しました。

理由は、メンテナン状況や、ドキュメントが読みやすく整備されているからです。

以上、 stylelint-scssstylelint-config-sass-guidelines は、 .stylelintrc.js に記述して使います。

Prettier

コードを規約により、整形します。

stylelint-prettier

Prettierをstylelintと統合して実行します。

stylelint-config-prettier

stylelint上のコードフォーマット(整形)に関するルールを無効にします。

Prettierとstylelintと競合を回避して、Prettierが優先されるようになります。

stylelint-scss

scssシンタックスに対応するルールをstylelintに追加するプラグインです。

インストール

$ npm i —save-dev stylelint stylelint-scss stylelint-config-sass-guidelines prettier stylelint-prettier stylelint-config-prettier

.stylelintrc.js

module.exports = {
  plugins: [
    "stylelint-scss",
    "stylelint-prettier"
  ],
  extends: [
    "stylelint-config-sass-guidelines",
    "stylelint-config-prettier"
  ],
  rules: {
    "prettier/prettier": true
  }
}

参考

ESLint

ESLintは、Javascriptの静的解析ツールです。

問題があるパターンや、ガイドラインに準拠していないコードを指摘します。

JavaScriptは動的で型付が緩い言語なので、開発者がJavaScriptを実行することなく問題を発見し、エラーを防ぐために有効です。

Prettier

eslint-plugin-prettier

PrettierをESLintと統合して実行します。

eslint-config-prettier

ESLint上のコードフォーマット(整形)に関するルールを無効にします。

PrettierとESLintと競合を回避して、Prettierが優先されるようになります。

インストール

$ npm i --save-dev eslint prettier eslint-plugin-prettier eslint-config-prettier

初期設定

ルールは、standardスタイルを選択しました。

$ npx eslint --init  

初期化実行後に、 .eslintrc.json が生成されます。

.eslintrc.json

{  
    "extends": [  
        "standard",  
        "prettier"  
    ],  
    "plugins": [  
        "prettier"  
    ],  
    "rules": {  
        "prettier/prettier": "error"  
    }  
}  

参考

BrowserSync

BrowserSyncとは、コーディングを行うと、自動でブラウザリロードを行ってくれるツールです。

作業とデバッグを交互にテンポを良く行うために有効です。

BrowserSyncを起動させる時のオプションを説明します。

npm-scripts

"sync": "browser-sync start -s './src' -w 'src/*.html' 'src/css/*.css' 'img/*' 'src/js/*.js'",

browser-sync start

BrowserSyncを起動します。

-s オプション

起動したローカルサーバーのルートディレクトリを指定します。

-w オプション

監視するファイルを指定します。

監視に指定されたファイルを更新すると、ブラウザがリロードします。

まとめ

おわかりいただけただろうか…。

フロントエンドの開発環境を整備するのは、たくさんのパッケージが必要になります。

これらのツールのメンテナンスは盛んに行われおり、その影で目まぐるしくツール(パッケージ)の興廃が起こっています。

しかし、必ず解決すべき課題があってそれらのツールは誕生しているので、落ち着いてツールの選定を行っていきましょう。

また、トレンドを注視して追いかける必要もあります。

最近は、Parcelや、Nuxt.js、Vue-Cli v3など、ワンストップで瞬時に全部入りの環境を用意できるようになって来ました。

良い時代になったものです。

次回は、Parcelを使って同様の環境を再現し、実用性を検証したいと思います。