Smart Communication Design Company
ホーム > ナレッジ > Blog > フロントエンドBlog

フロントエンドBlog

Webのフロントエンドを構成する技術、特にHTMLやCSS、JavaScript、またそれらに関連する話題を扱うBlogです。

BabelとPostCSS、ESLintとstylelintの設定をpackage.jsonに詰め込んでみる

UI開発者 郡司

Web開発の効率化や品質向上のために日々多くのツールやパッケージが登場しています。
Web開発を行う時に「このツールも使おう、あのパッケージも使ってみよう!」といろいろ導入した結果、ディレクトリ内に設定ファイルが溢れ返ってしまい管理が大変になることがあります。

Node.jsで作成されたパッケージの中にはpackage.json内に設定が記述できるものがあります。
今回はWeb開発でよく使われている以下のパッケージの設定をpackage.json内に書く方法をご紹介します。

サンプルのディレクトリ構造

今回使用するサンプルのディレクトリ構造は以下になります。

./
├─package.json
├─postcss.config.js
│
├─public
│  ├─css
│  │  └style.css
│  │
│  └─js
│     └script.js
│
└─src
    ├─css
    │  ├style.css
    │  │
    │  └vendor
    │    └normalize.css
    │
    └─js
       └script.js

必要なnpmのインストール

では早速、package.jsonを生成して各パッケージをインストールしましょう。

npm install eslint stylelint stylelint-config-standard postcss-cli autoprefixer postcss-import babel-cli babel-preset-env@2.0.0-beta.0 --save-dev

package.jsonの追記

インストールが完了したら、package.jsonに以下を追記します。

{
  "name": "sample",

  ~~~ 省略 ~~~

  "scripts": {
    "postcss": "postcss ./src/css/style.css -c ./postcss.config.js -o ./public/css/style.css",
    "babel": "babel ./src/js/script.js -o ./public/js/script.js",
    "stylelint": "stylelint ./src/css/**/*.css !./src/css/vendor/*.css",
    "eslint": "eslint ./src/js/**"
  },
  "browserslist": [
    "last 1 versions"
  ],
  "babel": {
    "presets": [
      [
        "env",
        {
          "modules": false,
          "useBuiltIns": "usage",
          "debug": true
        }
      ]
    ]
  },
  "postcss": {
    "map": true,
    "plugins": {
      "postcss-import": {},
      "autoprefixer": {}
    }
  },
  "eslintConfig": {
    "extends": "eslint:recommended",
    "env": {
      "browser": true
    },
    "rules": {
      "semi": "error"
    }
  },
  "stylelint": {
    "extends": "stylelint-config-standard",
    "rules": {
      "color-hex-length": "long"
   }
  }
}

package.json内に任意の名前のフィールドを設け、その中に各パッケージの設定を書いていきます。

また、browserslistというフィールドを作成して、対象ブラウザを指定するとAutoprefixerの設定やBabelの設定に対象ブラウザを書く必要が無くなります。対象ブラウザの記述方法はこちらをご参照ください。

BabelとPostCSSの注意点

package.json内に設定を記述するにあたって、BabelとPostCSSでは注意が必要です。

PostCSSはpostcss.config.jsファイルを別途作成する必要があります。postcss.config.jsファイルの中で、package.jsonに記述された設定をプロパティ値として与えています。

postcss.config.jsファイル:

module.exports = (ctx) => ({
    map: ctx.map
    plugins: [
        require('postcss-import')({}),
        require('autoprefixer')({})
    ]
});

Babelに関しては、新しいJavaScriptの文法や機能に対応していない環境でも使用できるように変換するbabel-preset-envが、現時点の安定版ではbrowserslistを読み込むことができません。

利用可能となるのはバージョン2.0からになりますので、今回のサンプルでもバージョン2.0をインストールしています。

しかし、筆者の環境ではアロー関数が対応していない環境を対象に入れるとbabel-preset-envでエラーが出るなど不安定な部分もあり、まだ実務で使用するには早い印象を受けました。

まとめ

本来ならルートディレクトリに3つファイルが存在するはずでしたが、package.json内に記述することで管理するファイルが減りました。
他の人へ引き継ぎする際に、ファイルが多かったりすると説明する側も説明を受ける側もどっちも大変だったりします。また、1箇所で管理できれば開発環境をアップデートした際にも更新の見落としが減るでしょう。

他にも、ESDocというJavaScriptのドキュメント作成ツールも、package.jsonに設定を記述することができたりします。もしかしたら、お使いのパッケージの設定がpackage.jsonでも設定可能となっているかもしれません。

しかし、複数人で開発する場合などは開発者ごとに自由に設定を変更できないデメリットもありますので、開発者の人数によっては設定ファイルを分けたほうが効率がよい場合もあります。

これを機会にpackage.json内に設定に記述するパッケージと、他の開発者のためにあえて設定ファイルを分けるパッケージなど、先を見越した開発環境の作成を行っていきましょう。

ECMAScript (ECMA-262) が20歳になりました

UI開発者 渡邉

2017年9月18日、ECMAScript (ECMA-262)――JavaScriptの基礎となる仕様――が20歳を迎えました。

これを記念して、ECMAではバースデー文書が公開されています。その中では、1996年~1997年に公開された興味深い3つの文書がとりあげられています。

1つめは、 第1回TC39会議議事録(注:Microsoft Word文書)です。この文書は、ECMAScript誕生前夜の記録というべきもので、Brendan EichやCarl Cargilといった現在でも仕様・組織に関わり続けている人物の名前を見ることができます。

2つめは、1997年10月のTC39チェアマン報告書(注:PDFファイル)です。この文書では、プログラミング言語名としてはっきり「ECMAScript」と明記されています。また「JavaScript」や「JScript」という名称・歴史にも軽く触れています。

3つめは、第5回TC39会議議事録(注:PDFファイル)です。この文書には、プログラミング言語名についての議論が含まれています。「JavaScript」や「LiveScript」といった名称は使用できないという判断のもと、試行錯誤のすえに「ECMAScript」で合意がとれた旨が記されています。

こうして歴史をふりかえる機会があると、感慨深いものがありますね。

少し遅くなりましたが、ハッピーバースデー。ECMAScript。

20行でできる!JavaScriptで作る簡単なフィルタ機能

UI開発者 宇賀

製品一覧やセミナー一覧など、複数のアイテムが並んでいるページでは、たびたびフィルタ機能が求められることがあります。

ここで言うフィルタとは、ある程度の量がある情報の中からユーザーが求めている情報(アイテム)だけに絞り込む機能を指します。

フィルタ機能の実装方法

フィルタ機能を実装する方法にはいくつか手段があります。たとえば次のようなパターンです。

いずれもメリットとデメリットが異なります。例に挙げたもの以外の方法も含め、要所要所で最適な方法で実装するべきでしょう。

今回は例の中で登場した3個目のパターンについて取り上げたいと思います。

実際のコード

フィルタ機能を実現するための簡易的なマークアップを用意します。実装する具体的な内容は「選択されたカテゴリに応じて、お寿司のネタを出しわける」というものです。

※ 次のサンプルではHTMLの中にCSS(style要素)が記述されていますが、通常は外部リソースにしてlink要素で読み込むべきです。

<style>
[data-filter-view]:not([data-filter-view=""]) [data-filter-key] {
    display: none;
}
[data-filter-view][data-filter-view~="貝類"] [data-filter-key="貝類"],
[data-filter-view][data-filter-view~="光物"] [data-filter-key="光物"],
[data-filter-view][data-filter-view~="白身"] [data-filter-key="白身"],
[data-filter-view][data-filter-view~="赤身"] [data-filter-key="赤身"] {
    display: block;
}
</style>

<div class="filter" id="js-filter">
<ul class="filter-cond">
<li><label><input type="checkbox" value="貝類">貝類</label></li>
<li><label><input type="checkbox" value="光物">光物</label></li>
<li><label><input type="checkbox" value="白身">白身</label></li>
<li><label><input type="checkbox" value="赤身">赤身</label></li>
<!-- /.filter-cond --></ul>

<ul class="filter-items">
<li data-filter-key="貝類">つぶ貝</li>
<li data-filter-key="白身">カンパチ</li>
<li data-filter-key="赤身">マグロ</li>
<li data-filter-key="光物">コハダ</li>
<li data-filter-key="貝類">バイ貝</li>
<li data-filter-key="白身">マダイ</li>
<li data-filter-key="貝類">サザエ</li>
<!-- /.filter-items --></ul>
<!-- /#js-filter--></div>

続いて、前述のHTMLとCSSでフィルタ機能を動かすためのJavaScriptコードを用意します。

全文を読む

ESLintのルールを自作しよう!

UI開発者 加藤

ESLintとは指定したルールに沿ってJavaScirptコードをチェックしてくれるツールです。明らかなバグを減らしたり、複数人で作業したときでも書き方を統一させるのに役立ちます。

主な特長として、使用したいルールをピックアップして自分だけのルールセットを作ったり、各種ライブラリが公開している設定ファイルを使ったりなど柔軟なカスタマイズが可能であることがあげられます。ECMAScript 6用のルールも既に定義されており、合計で約250項目ものルールを提供してくれています。ESLint公式サイトのList of available rulesページに定義済みのルールが一覧されていますので、興味がある方はご覧ください。

今回はまだ定義されていないルールを自作する方法をご紹介したいと思います。なお、ご紹介する方法はESLintのバージョン4.6.1時点でのものです。バージョンアップにより記法が変わる場合がありますのでご注意ください。

テーマ

今回のサンプルとして作成するルールは「jQueryのセレクタには子孫セレクタは使わず、メソッドチェーンを使おう」です。既にjQueryのプラグインは提供されておりますが引数などの細かいルールは定義されていないようです。

フォルダ構成

実際にコードをチェックする「rules/no-descendant-combinator.js」と、そのチェック自体に問題がないかをテストする「test/no-descendant-combinator.js」を作成します。自作ルールが増えたときに互いに対応するファイルがわかりやすいようにファイル名を同じにします。今回は下記のようなフォルダ構成を想定しています。

lib/
 ├ rules
 │   └ no-descendant-combinator.js
 └ test
      └ no-descendant-combinator.js
ルールの記述方法

Working with Rulesページに、ESLintにおけるルール定義の基本構造が示されています。

module.exports = {
    meta: {
        docs: {
            description: "disallow unnecessary semicolons",
            category: "Possible Errors",
            recommended: true
        },
        fixable: "code",
        schema: [] // no options
    },
    create: function(context) {
        return {
            // callback functions
        };
    }
};

metaにはルール自体の情報を定義します。自作ルールの場合はdocsは省略可能です。fixableも自動修正できないルールに関してはキーごと削除して問題ありません。schemaは特にオプションの指定がない場合は空の配列を指定します。

最低限ではありますが、今回のテーマに合わせて記述した「rules/no-descendant-combinator.js」が下記です。

// rules/no-descendant-combinator.js
const ERROR_MESSAGE = '子孫セレクタの使用は非推奨です。';
module.exports = {
    meta: {
        docs: {
            description: "disallow descendant-combinator"
        },
        schema: [] // no options
    },
    create: function(context) {
        function checkString (node) {
            if ((node.value.indexOf(' ') !== -1)) {
                context.report({
                    node: node,
                    message: ERROR_MESSAGE
                });
            }
        }
        return {
            Literal: function(node) {
                if (typeof node.value === "string") {
                    checkString(node);
                }
            }
        };
    }
};

実際にコードをチェックするロジックを書いていく部分がcreate関数の中になります。この部分はASTの仕様に沿って記述します。

ASTとは

AST構造はコードから言語の文法的な意味だけを切り取り、「この部分は値を定義している部分」、「この関数の範囲は何行目~何行目」といった情報を持ちます。

ESLintでは実際のコードをAST構造に変換してからチェックを行っています。JavaScriptのAST構造には様々な仕様がありますが、今回はESTreeという団体が策定している仕様で進めていきます。コードをASTに変換するにはESLintプロジェクトで使用されているespreeというパーサーが便利です。

実際にどのような変換が行われるかを見てみましょう。たとえば下記のような変数を定義する一文があるとします。

var message = "Hello world!";

このコードをespreeを使ってAST構造に変換すると、下記のような木構造が出来上がります。

{
    "body": [{
        "declarations": [{
            "id": { "type": "Identifier", "name": "message" },
            "init": { "type": "Literal", "value": "Hello World!" }
            ...
        }],
        "type": "VariableDeclaration",
        ...
    }]
}

一部だけ抜粋しましたが、変数定義の一文は「typeVariableDeclarationで、変数messageHello Wolrd!という値を代入している」という構造に変換されました。ここで重要なのがtypeの値です。先ほどの「rules/no-descendant-combinator.js」のcreate関数の中で、Literalという名前の関数を定義しています。このLiteralは実はASTのtypeの値と対応しています。AST構造を上から順に下っていき、typeLiteralの記述が見つかったら実際にその部分に関してチェックを行います。実際にルールを書く際はこのASTの仕様と見比べながら書いていくようになります。

テストファイルの作成

続いてはルールのロジックにバグがないかをテストするファイルを作ります。ESLintプロジェクトで使用されているRuleTesterモジュールを使用します。

// test/no-descendant-combinator.js
let rule = require("../rules/no-descendant-combinator");
let RuleTester = require('eslint').RuleTester;
let ruleTester = new RuleTester();

const ERROR_MESSAGE = '子孫セレクタの使用は非推奨です。';

ruleTester.run("no-descendant-combinator", rule, {
    valid: [
        '$(".parent").find(".child")'
    ],
    invalid: [
        {
            code: 'var $child = $(".parent .child")',
            errors:[{message: ERROR_MESSAGE, type: "Literal"}]
        }
    ]
}

validにはテストを通過することが期待されるケースを書いていきます。invalidにはテストに失敗することが期待されるケース、ASTのタイプ、エラーメッセージをオブジェクト型で書いていきます。その他にもオプションが多数ありますので、Rule Unit Testsの項目をご覧ください。

先のコードでは合計2回のテストが行われ、その内1回が通過、もう1回が失敗することが期待されています。実際にテストを実行してみます。テストフレームワークとしてmochaを使用します。

$ mocha lib/test/no-descendant-combinator.js

no-descendant-combinator
  valid
    √ $(".parent").find(".child") (61ms)
  invalid
    √ var $child = $(".parent .child")
2 passing (80ms)

片方が通過、片方が失敗し、テスト全体としては成功していることがわかります。実際は意図しないバグを減らせるようvalid,invalidのケースをさらに増やす必要があります。ちなみにESLintが定義している「セミコロン必須」のルールに対するテストケースはvalid,invalid合わせて130パターンほど定義されています。

また現状のロジックでは全てのLiteralタイプに対してチェックを行ってしまいます。たとえば、ただ変数に文字列を代入するだけでも値にスペースが含まれているとエラーになってしまうでしょう。今回は割愛しますが、他のタイプをうまく使いながら条件を絞っていく必要があります。

自作したルールを実際に使用する場合は、eslint実行時に--rulesdirオプションでルールファイルのあるディレクトリを指定するだけでその階層にあるルールを全て適用してくれます。

$ eslint hoge.js --rulesdir 'lib/rules/'

以上でルールの作成は完了です。長い道のりではありますが、一度ルールを作ってしまえば以降のコーディング作業の効率をアップさせ、品質を上げることができると思います。より良いコーディングライフのための自分なりのルールを作ってみてはいかがでしょうか。

AMP HTMLでフォームを実装する

UI開発者 木村

AMP発表当初はニュースやブログなどの記事ページでの対応が推奨されており、form要素やinput要素などフォームに関連する要素を使用するとAMP HTMLの制約違反となっていました。現在ではAMP HTMLの開発が進みフォームを実装するためのamp-formというコンポーネントや、インタラクティブな機能を実装するためのamp-bindが一般に公開され、AMP対応可能なWebサイトの幅が広がりました。

今回はAMP HTMLでフォームを実装する方法をご紹介します。

AMP HTMLでフォームを実装するためにはamp-formというコンポーネントを使用します。amp-formを使用することでフォーム関連のHTML要素が使用可能になり、執筆時点では次のHTML要素が使用可能になります。

通常のHTMLとの主な違い
form要素にtarget属性が必須

form要素にtarget属性が必須となります。target属性はフォーム送信時に送信先のページをブラウザの同一のタブで表示するか、新規のタブで表示するかを指定します。同一のタブの場合は_top、新規のタブの場合は_blankを指定します。

form要素のイベント

form要素にon属性を用いてイベントを付与することが可能です。設定できるイベントはsubmitsubmit-successsubmit-errorの3つで、送信時や送信結果に応じてモーダルの出し分けなど異なる処理を行うことが可能です。

また、form要素内のHTML要素にsubmit-successsubmit-errorの属性を付与すると、初期状態が非表示になり送信結果に応じてその要素を表示させることも可能です。

action-xhr属性

通常のHTMLではフォームの送信先をaction属性を用いて指定しますが、amp-formでPOST送信を行う場合は、action属性の代わりにaction-xhr属性を使用する必要があります。

action-xhr属性はaction属性と異なり、送信時にAMP JS内でFetch APIやXMLHttpRequest APIを用いてPOST送信が行われます。そのため、デフォルトでは送信後にそのページには遷移しません。送信後にページ遷移させるには送信先のHTTPヘッダにAMP-Redirect-Toを設定してリダイレクトさせる必要があります。

POST送信の場合は送信先にHTTPヘッダを設定する

amp-formでPOST送信を行う場合、送信先のHTTPヘッダにAMP-Access-Control-Allow-Source-Originを追加する必要があります。AMP-Access-Control-Allow-Source-Originには送信元のドメインを指定します。

その他にもamp-formで使用可能な機能や、固有のHTTPヘッダなどありますが機能が豊富なため今回は割愛します。

実装サンプル

今回は、amp-mustacheというコンポーネントを併用し、送信結果をページ内に表示します。amp-mustacheはmustache.jsを元にAMP用のコンポ―ネントとして実装されたテンプレートエンジンです。

次が実際にフォームを実装したHTMLのサンプルソースです。

<!DOCTYPE html>
<html lang="ja" amp>
<head>
<meta charset="utf-8">
<link rel="canonical" href="./">
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0">
<title>amp-form sample</title>
<style amp-custom>
*{padding:0;margin:0;box-sizing:border-box}main{padding:16px}fieldset{padding:0;border:0;margin:0 0 16px}.btn-submit,.input-text>input{display:block;border:1px solid #999}.input-text{margin:0 0 8px;display:block}.input-text>span{display:block;margin:0 0 4px}.input-text>input{width:100%;height:32px;padding:4px}.btn-submit{width:120px;background:#fff;font-size:16px;min-height:32px;margin:24px 0 18px}.result{color:green}
</style>
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
<script async src="https://cdn.ampproject.org/v0.js"></script>
<script async custom-element="amp-form" src="https://cdn.ampproject.org/v0/amp-form-0.1.js"></script>
<script async custom-template="amp-mustache" src="https://cdn.ampproject.org/v0/amp-mustache-0.1.js"></script>
</head>
<body>
<main>
<form method="post" target="_top" action-xhr="confirm.php">
<fieldset>
<label class="input-text">
<span>お名前</span>
<input type="text" name="name">
</label>

<label class="input-text">
<span>メールアドレス</span>
<input type="email" name="email">
</label>

<button type="submit" class="btn-submit">確認</button>
</fieldset>

<div submit-success class="result">
<template type="amp-mustache">
<p>お名前: {{name}}</p>
<p>メールアドレス: {{email}}</p>
</template>
</div>

<div submit-error class="result">
<template type="amp-mustache">
<p>送信に失敗しました</p>
</template>
</div>
</form>
</main>
</body>
</html>

また、POST送信の際に送信先にHTTPヘッダが必要なことと、action-xhr属性はJSON形式でのレスポンスを受け取る必要があるので、confirm.phpに次のようなPHPソースを記述し、簡易的にフォームで送信した内容をそのままJSON形式に変換して出力します。

<?php
header('AMP-Access-Control-Allow-Source-Origin: https://example.com');

echo json_encode($_POST, true);
?>

AMP-Access-Control-Allow-Source-Originの値は、送信元のドメインを記述します。

実際に送信した結果が次のキャプチャです。

POST送信が成功したのでdiv[submit-success]要素が表示され、その中のtemplate[type="amp-mustache"]にフォームで送信した内容をテンプレート出力できています。

form要素が使用可能になったことで登録系のフォームやECサイトなどもAMP対応が可能になりました。その他のジャンルのページやサービスでも、アイデア次第でAMP対応が可能だと思われますので是非ご検討ください。また、個人的にこれからもAMPの活用の幅が広がることを期待しています。