もがき系プログラマの日常

もがき系エンジニアの勉強したこと、日常のこと、気になっている技術、備忘録などを紹介するブログです。

今から始めるWebフロントエンド開発を読んで勉強した(パート1)

こんにちは。

フロントの勉強するにあたって、最近のモダンなフロントエンド開発を知らないため、コチラの本を購入しました。

Backbone.jsから止まっている自分の時間をすすめるため、頑張って勉強しました。

コチラの本では、3分の1以上がJSの歴史と、現代の仮想DOM時代への流れが記載されていました。

その後簡単なTODOアプリを作成します。その際に、モダンなフロントエンドの技術を用いて開発を行います。

それぞれ名前はしっているけど、実際に使用したことはほぼないものばかりでした。

今回はこちらを自分なりに噛み砕きながら、覚えるためにブログ記事にしていこうと思います。

使用したツール群

BABEL

ES2015準拠で書かれたJSコードをES5のもの(各ブラウザで動かせるもの)に変換してくれるトランスパイラというものです。

開発時にES2015準拠でコードを書いていき、製品版として仕上げる際は、BABELを噛ませてES5のものに変更するという使い方だと思います。

インストール

# Babelのインストール
$ npm install --save-dev babel-cli

# ES2015からの変換を行うプリセットをインストール
$ npm install --save-dev babel-preset-es2015

セットアップ

.babelrc というファイルが、babelの設定ファイルとなります。コチラを作成し、以下になるように編集してください。

$ cat .babelrc 
{
  presets: ["es2015"]
}

使ってみる

テスト用のjsファイルを作成して、以下のコードを書いてみました。

# test.js
class A {
  constructor() {
    this.name = 'Aですよ';
  }

  getName() {
    return this.name;
  }
}

こちらをBabelで変換してみます。

変換のコマンドは以下になります。

# -oでファイルに書き出しする
node_modules/.bin/babel test.js -o compiled.js

出来上がったファイルの中身は以下になります。

# compiled.js

'use strict';
  
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var A = function () {
  function A() {
    _classCallCheck(this, A);

    this.name = 'Aですよ';
  }

  _createClass(A, [{
    key: 'getName',
    value: function getName() {
      return this.name;
    }
  }]);

  return A;
}();

正常に変換されているようでした。

Browserify / WebPack

各JSファイルの依存関係をよしなに解決してくれて、一つにまとめてくれる優れものツールということでした。

WebPackに関しては、JSに限らず、cssや画像などもまとめて1つのjsファイルにしてくれるようです。

該当のJSファイルに書かれているrequire関数を読み取り、依存関係を解決してくれます。

require関数とは?と思って調べると、CommonJS Modulesというものに含まれているということでした。

CommonJSとは?と調べると、以下引用です。

CommonJSとは、サーバーサイドなどのウェブブラウザ環境外におけるJavaScriptの各種仕様を定めることを目標としたプロジェクトである。

ツールというより仕様ということでした。

NodeJSのモジュールシステムはこれを元に動いているようです。

大本ということなのでしょうか?

どちらにしても、nodejs経由でインストールしているため、どちらもrequire関数を検知できるということなのだと理解しました。

(もう一つのモジュール管理でAMDというものがあり、そちらにRequireJSというのがあるようです。こちらもrequire関数と同等のものが使え、webpackに関してはコチラも対応しているということでした)

こちらの本では、Browserifyを使用していたので、自分も例にそって同じものを使ってみます。

インストール

$ npm install --save-dev browserify

使ってみる

早速使ってみます。 以下のような3つのJSファイルを作成します。

# test1.js
let a = require('./test2.js');
console.log(a.getName());

let b = require('./test3.js');
console.log(b.getName());
# test2.js
class A {
    constructor() {
      this.name = 'my name is A!';
    }

    getName() {
        return this.name;
    }
}
module.exports = new A;
# test3.js
class B {
    constructor() {
      this.name = 'my name is B!';
    }

    getName() {
        return this.name;
    }
}
module.exports = new B;

test1.jsはtest2.jsとtest3.jsに依存しているものがわかると思います。

こちらをbrowserifyで変換してみます。

$ node_modules/.bin/browserify test1.js -o bundle.js

後は確認のため適当なHTMLを作成します。

# test.html
<!DOCTYPE html>
<html>
<head>
    <title>test</title>
</head>
<body>
    <script type="text/javascript" src="bundle.js"></script>
</body>
</html>

test.htmlをブラウザで開きconsole.logを確認すると、以下のようになると思います。

f:id:kojirooooocks:20180115010855p:plain

実際の開発現場では、ES2015でJavaScriptコードを書いていき、Babelで各ブラウザ対応のJSへ変換し、Browserify(WebPack)を使用して依存関係を解決し1つにまとめるというのが流れのようです。

(この本は2016年8月くらいの本なのですが、今もそうなんでしょうか・・・?知ってる人いたら教えてください)

Babelify

コード修正の度に、Babelコマンド叩いて、Browserifyを叩いてとするのはとてもめんどいです。

さらに以下、引用ですが、重要なところが書かれていました。

require関数はもともとはNode.jsで用意されていた関数で、importやexportはES2015で定義された仕様です。これに対してBabelは、importをrequire関数に変換しますが、多くのブラウザではrequire関数を読み込めず、モジュール間の依存解決を解決できません。

たとえば、A.jsでimportを、B.jsでexportを使用している場合、それぞれBabelを噛ませることで各ブラウザ対応のJSへ変更してくれます。

その際、A.jsのimportはrequireに変換されますが、対象ファイル自体はBabelを実行する前のES2015で記述されているファイルになります。

Browserifyを実行する前にわざわざファイルを開いて、Babel実行後のファイルに向き先を変更して上げる必要があります。

これはあまりにつらい作業です。。。

その辺の辛さを解消してくれるのがBabelifyというツールです。

BabelifyはBrowserifyの変換時にES2015の変換も同時に行ってくれるものです。つまりBabelの仕事も一緒にやってくれるということのようです。

インストール

$ npm install --save-dev babelify

使ってみる

例となるスクリプトはありませんが、以下が実行コマンドになります。

# -tオプションで babelifyを指定する
$ node_modules/.bin/browserify A.js -t babelify -o bundle.js

ESLint

本ではその他の取り組みとして、JSコードの静的解析を行うツールとして名前だけ紹介されていましたが、今回せっかくなので、どんなものか試してみたいと思います。

コチラを参考にしました。

インストール

$ npm install --save-dev eslint

セットアップ

eslintのセットアップを行います。

色々と質問されます。

CommonJS使いますか?JSX使いますか?React使いますか?などなど。

Yesとすると追加パッケージを落とすこともあります。 (Reactとかはそう)

$ ./node_modules/.bin/eslint --init
? How would you like to configure ESLint? Answer questions about your style
? Are you using ECMAScript 6 features? Yes
? Are you using ES6 modules? Yes
? Where will your code run? Browser
? Do you use CommonJS? Yes
? Do you use JSX? No
? What style of indentation do you use? Spaces
? What quotes do you use for strings? Double
? What line endings do you use? Unix
? Do you require semicolons? Yes
? What format do you want your config file to be in? JSON

回答が終了すると以下のようなファイルが作られます。

# .eslintrc.json
{
    "env": {
        "browser": true,
        "commonjs": true,
        "es6": true
    },
    "extends": "eslint:recommended",
    "parserOptions": {
        "sourceType": "module"
    },
    "rules": {
        "no-console": "off",
        "indent": [
            "error",
            4
        ],
        "linebreak-style": [
            "error",
            "unix"
        ],
        "quotes": [
            "error",
            "double"
        ],
        "semi": [
            "error",
            "always"
        ]
    }
}

使ってみた

eslintのテストをするため以下のファイルを作成しました。

# eslint_test.js
class Cats {
    Constructor(name) {
        this.name = name;
    }

    getName() {
        return this.name;
    }
}

let cats = Cats('ドラえもん');
console.log(cats.getName())

このファイルを対象にeslintを実行してみます。

$ ./node_modules/.bin/eslint eslint_test.js

/home/vagrant/eslint_test.js
  11:17  error  Strings must use doublequote  quotes
  12:1   error  Unexpected console statement  no-console
  12:28  error  Missing semicolon             semi

✖ 3 problems (3 errors, 0 warnings)
  2 errors, 0 warnings potentially fixable with the `--fix` option.

ダブルクォート使えよっていうのと、セミコロン付け忘れてるぞ!っていうのは理解できました。

no-consoleというのはなんだろうと、調べていると、最初のeslintが設定してくれているおすすめ設定の中に、console記述禁止の設定があるようです。

console.logは使いたいので、rulesを書き変えてみます。

.eslintrc.jsonrules の部分に以下を追記します。

"no-console": "off",

セミコロンとダブルクォートの修正も行い、再度実行してみます。

$ ./node_modules/.bin/eslint eslint_test.js

何も表示されなくなりました。つまり静的解析は問題なく通っているということになります。

便利ですね!

uglify

javascriptコードのminifyを行うさいには uglify を使うようです。

こちらは本で紹介されていませんが、まぁ実際にアプリケーションを使って本番に公開する際はきっと使うことになるでしょうし、実際使ってみます。

ちなみにこちらはgulp経由で使用した経験はありますが、ググりながらコピペで使ってたので、理解はしていませんでした。。

インストール

$ npm install --save-dev uglify

使ってみた

上の方でbabelコマンドを使って変換したjsファイルで実験してみます。中身は以下。

'use strict';

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var A = function () {
  function A() {
    _classCallCheck(this, A);

    this.name = 'Aですよ';
  }

  _createClass(A, [{
    key: 'getName',
    value: function getName() {
      return this.name;
    }
  }]);

  return A;
}();

以下のコマンドを実行します。

$ node_modules/.bin/uglifyjs compiled.js -c -m -o compiled.min.js

実行で出来たファイルの中身は以下。

"use strict";function _classCallCheck(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}var _createClass=function(){function e(e,n){for(var t=0;t<n.length;t++){var a=n[t];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),Object.defineProperty(e,a.key,a)}}return function(n,t,a){return t&&e(n.prototype,t),a&&e(n,a),n}}(),A=function(){function e(){_classCallCheck(this,e),this.name="Aですよ"}return _createClass(e,[{key:"getName",value:function(){return this.name}}]),e}();

圧縮・難読化されていました!

終わり

なんか周辺ツールの勉強だけで結構なボリュームになってしまった。。。

TODOアプリはReactを使って作成するので、今度はそちらの勉強記事をあげようと思います。

では。