7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ハンズオン:今時のフロントエンド開発の最初の一歩

Last updated at Posted at 2020-03-09
1 / 75

はじめに

この記事は、今時のフロントエンド開発の最初の一歩を踏み出すために理解すべきことを理解する、そして、実際にハンズオン形式で最初の一歩を踏み出すところまでを行いながら理解を深める、という目的のハンズオン資料です。

社内勉強会のために作成した物を一般向けに改善したものとなります。


この記事のターゲット

  • 今時のフロントエンド開発が昔とどう違うのか知りたい人
  • 今時のフロントエンド開発の前提を学びたい人
  • 今時のフロントエンド開発を実際にハンズオンで学びたい人

ES6+とは

ECMAScript6以降のこと
IEでは動かない新しい構文を含む
IEでは動かない新しい機能を含む


ES6+の例

// const, デフォルト引数, アロー関数
const maskKey = (key, padString = '*') => {
  // const
  const sliced = key.slice(-4);
  // String.prototype.padStart
  return sliced.padStart(key.length, padString);
};

console.log(maskKey('abcdefghij')); // "******ghij"
console.log(maskKey('abcdefghij', 'x')); // "xxxxxxghij"

いずれもES5環境(IE11)では動かないES6+の構文・機能

const - JavaScript | MDN
デフォルト引数 - JavaScript | MDN
アロー関数 - JavaScript | MDN
String.prototype.padStart() - JavaScript | MDN


前時代のフロントエンド


HTML+CSS+jQuery

  • さらに +jQueryMobile+jQueryUI+bootstrap... だったり
  • 書いたコードがそのままブラウザで動くコード
    • ターゲットブラウザを意識して書く必要がある
    • ファイル数、モジュール数の分ファイルダウンロードさせる必要がある

ターゲットブラウザを意識して書く必要がある

  • IE11がターゲットに入っているからアロー関数は使わない
  • IE8がターゲットに入っているからArray.prototype.forEachは使わない
  • などなど

ファイル数、モジュール数の分ファイルダウンロードさせる必要がある

  • jQuery,bootstrap.css,bootstrap.js,...
    • 必要なモジュールの数だけユーザーのダウンロードファイル数が増える
  • 共通のjsやページごとのjsなど
    • 管理のためにファイル分割するとユーザーのダウンロードファイル数が増える

前時代のフロントエンド:js,cssの読み込み

<html>
  <head>
    <link rel="stylesheet" href="bootstrap.css" />
    <link rel="stylesheet" href="my-common.css" />
    <link rel="stylesheet" href="my-page.css" />
    <script src="jquery.js"></script>
    <script src="jquery-ui.js"></script>
    <script src="bootstrap.js"></script>
    <script src="moment.js"></script>
    <script src="alertify.js"></script>
    <script src="my-common.js"></script>
    <script src="my-page.js"></script>
  </head>
  <body>
    ...
  </body>
</html>

今時のフロントエンド


  • 書いたコードをそのままブラウザが実行するわけではない

ターゲットブラウザを意識しなくていい

  • ES6+で書いて未対応ブラウザのためにtranspileする
  • 未対応ブラウザでも使いたい機能が動くようpolyfillする

ファイル数、モジュール数はユーザーのダウンロードファイル数と関係ない

  • ソースコードを分割して管理し、bundleしてまとめた1つの物をユーザーがダウンロードする

ソースコードの改行・インデントや変数名の長さはユーザーのダウンロードファイルの容量と関係ない

  • ソースコードをminifyして圧縮された物をユーザーがダウンロードする

今時のフロントエンド:js,cssの読み込み

<html>
  <head>
    <script src="bundle.js"></script>
  </head>
  <body>
    ...
  </body>
</html>

スタイル定義もjsに入っているのでcssの読み込み不要


transpile(トランスパイル)

  • あるプログラミング言語をインプットとして、同等のソースコードを別のプログラミング言語で出力すること
  • ES6+で書いたコードをES6+が動かないブラウザでも動作するES5に変換する

polyfill(ポリフィル)

  • 古いブラウザであってもモダンブラウザと同等の機能を提供すること
  • 例えばIEにString.padStartメソッドはない
  • String.padStartがなくてもpadStart相当のことを行う実装を入れること

transpile,polyfillの目的

  • 開発で使う言語使用・及び機能についてターゲットブラウザを意識しなくてよくなる
    • IE11がターゲットに入っていてもスマートなコーディングができる

bundle(バンドル)

  • ソースコードを組み合わせてひとまとめにすること

bundleの目的

  • 開発時はユーザーのことを気にせず好きなだけファイル分割できるので、ソースコード管理が楽になる
  • ユーザーがダウンロードするファイル数を減らせる

minify(ミニファイ)

  • ソースコードの実行時に不要となる改行やコメントの削除、変数の文字数を減らすなど同等の内容で短い記述にして、ソースコードの容量を減らすこと

minifyの目的

  • ユーザーがダウンロードするファイルの容量を減らせる

ここまでのまとめ


今時のフロントエンドは

  • transpileを前提として、好きな言語で書く
  • polyfillを前提として、ブラウザごとの機能の有無を気にせずに書く
  • bundleを前提として、適宜ファイル分割したりパッケージを使用して書く
  • minifyを前提として、変数名やコンポーネント名は長くてもいいから分かりやすく書く

実際に今時のフロントエンド開発環境を作ろう


作る物


入力した文字列をマスクした内容をalertで表示するページ
qiita_babel_webpack_sample.png


今回のゴール

  • babel+webpackでフロントエンド開発環境を作成できる
  • React,Vue,AngularのようなUIライブラリは含まない

使用するツールと役割

  • babel: transpile, polyfill
  • webpack: bundle, minify

事前準備

  • nodeのインストールが必要
  • インストール方法はなんでもOK
  • ターミナルで node -v でバージョン情報が出ればOK

実装


自分で手を動かしたい方向け

  • 空ディレクトリを作成する
  • どこでもOK。以下は例
    • mkdir ~/frontend-handson
    • cd ~/frontend-handson

または既に実装済みのものを動かせればよい方

  • 以下リポジトリをcloneする
    • git clone git@github.com:yas-tyoukan/babel-webpack-handson.git
    • cd babel-webpack-handson

初期化する

  • npm init # いろいろ聞かれるがenter連打でOK

htmlを作成

  • index.html
<!DOCTYPE>
<html>
<head>
    <meta charset="utf8"/>
    <title>sample</title>
</head>
<body>
Input key and Click button.
<input name="key" type="text" value="abcdefgh" />
<button type="button" id="sample-button">mask</button>
<script src="bundle.js"></script>
</body>
</html>


cssの作成

  • sample.css
button {
    border: 1px solid red;
}

jsの作成1

  • maskKey.js
// const, アロー関数, デフォルト引数,
const maskKey = (key, padString = '*') => {
  // const
  const sliced = key.slice(-4);
  // String.prototype.padStart
  return sliced.padStart(key.length, padString);
};

export default maskKey;

jsの作成2

  • sampleFormHandler.js
import maskKey from './maskKey';

const buttonEl = document.getElementById('sample-button');
const inputEl = document.querySelector('[name=key]');
buttonEl.addEventListener('click', () => {
  alert(maskKey(inputEl.value));
});

jsの作成3

  • entry.js
// polyfillを実現するライブラリのimport
import 'core-js/stable';
import 'regenerator-runtime/runtime';

// 作成したファイルのimport
import './sampleFormHandler';
import './sample.css';


環境構築


babel


babelとは

  • トランスパイル・ポリフィルを行うツール
  • ES6+で書いたのでそれを古いブラウザでも動くようにするのが目的

babelの環境構築

次のコマンドを実行してパッケージをインストールする


npm i core-js@^3.6.4 regenerator-runtime@^0.13.3
npm i -D @babel/core@^7.8.3 @babel/register@^7.8.3 @babel/preset-env@^7.8.3 @babel/cli@^7.8.3

babelの設定ファイル作成

  • .babelrc.js
module.exports = (api) => {
  api.cache(true);
  const presets = [
    [
      '@babel/preset-env',
      {
        targets: {
          chrome: '79',
          ie: '11',
          firefox: '72',
          safari: '13',
        },
        useBuiltIns: 'entry',
        corejs: 3,
        debug: true,
      },
    ],
  ];

  const plugins = [];

  return {
    presets,
    plugins,
  };
};


動作確認

maskKey.jsのトランスパイル

npx babel maskKey.js -o output.js

output.jsが作成される。中身を見るとトランスパイルされていることが分かる

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

// const, アロー関数, デフォルト引数,
var maskKey = function maskKey(key) {
  var padString = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '*';
  // const
  var sliced = key.slice(-4); // String.prototype.padStart

  return sliced.padStart(key.length, padString);
};

var _default = maskKey;
exports.default = _default;


entry.jsのポリフィル

npx babel entry.js -o output.js

結果

"use strict";

require("core-js/modules/es.symbol");

require("core-js/modules/es.symbol.description");

require("core-js/modules/es.symbol.async-iterator");

require("core-js/modules/es.symbol.has-instance");

require("core-js/modules/es.symbol.is-concat-spreadable");

require("core-js/modules/es.symbol.iterator");

require("core-js/modules/es.symbol.match");

require("core-js/modules/es.symbol.replace");

require("core-js/modules/es.symbol.search");

require("core-js/modules/es.symbol.species");

require("core-js/modules/es.symbol.split");

require("core-js/modules/es.symbol.to-primitive");

require("core-js/modules/es.symbol.to-string-tag");

require("core-js/modules/es.symbol.unscopables");

require("core-js/modules/es.array.concat");

require("core-js/modules/es.array.copy-within");

require("core-js/modules/es.array.every");

require("core-js/modules/es.array.fill");

require("core-js/modules/es.array.filter");

require("core-js/modules/es.array.find");

require("core-js/modules/es.array.find-index");

require("core-js/modules/es.array.flat");

require("core-js/modules/es.array.flat-map");

require("core-js/modules/es.array.for-each");

require("core-js/modules/es.array.from");

require("core-js/modules/es.array.includes");

require("core-js/modules/es.array.index-of");

require("core-js/modules/es.array.iterator");

require("core-js/modules/es.array.join");

require("core-js/modules/es.array.last-index-of");

require("core-js/modules/es.array.map");

require("core-js/modules/es.array.of");

require("core-js/modules/es.array.reduce");

require("core-js/modules/es.array.reduce-right");

require("core-js/modules/es.array.slice");

require("core-js/modules/es.array.some");

require("core-js/modules/es.array.species");

require("core-js/modules/es.array.splice");

require("core-js/modules/es.array.unscopables.flat");

require("core-js/modules/es.array.unscopables.flat-map");

require("core-js/modules/es.array-buffer.constructor");

require("core-js/modules/es.date.to-primitive");

require("core-js/modules/es.function.has-instance");

require("core-js/modules/es.function.name");

require("core-js/modules/es.json.to-string-tag");

require("core-js/modules/es.map");

require("core-js/modules/es.math.acosh");

require("core-js/modules/es.math.asinh");

require("core-js/modules/es.math.atanh");

require("core-js/modules/es.math.cbrt");

require("core-js/modules/es.math.clz32");

require("core-js/modules/es.math.cosh");

require("core-js/modules/es.math.expm1");

require("core-js/modules/es.math.fround");

require("core-js/modules/es.math.hypot");

require("core-js/modules/es.math.imul");

require("core-js/modules/es.math.log10");

require("core-js/modules/es.math.log1p");

require("core-js/modules/es.math.log2");

require("core-js/modules/es.math.sign");

require("core-js/modules/es.math.sinh");

require("core-js/modules/es.math.tanh");

require("core-js/modules/es.math.to-string-tag");

require("core-js/modules/es.math.trunc");

require("core-js/modules/es.number.constructor");

require("core-js/modules/es.number.epsilon");

require("core-js/modules/es.number.is-finite");

require("core-js/modules/es.number.is-integer");

require("core-js/modules/es.number.is-nan");

require("core-js/modules/es.number.is-safe-integer");

require("core-js/modules/es.number.max-safe-integer");

require("core-js/modules/es.number.min-safe-integer");

require("core-js/modules/es.number.parse-float");

require("core-js/modules/es.number.parse-int");

require("core-js/modules/es.number.to-fixed");

require("core-js/modules/es.object.assign");

require("core-js/modules/es.object.define-getter");

require("core-js/modules/es.object.define-setter");

require("core-js/modules/es.object.entries");

require("core-js/modules/es.object.freeze");

require("core-js/modules/es.object.from-entries");

require("core-js/modules/es.object.get-own-property-descriptor");

require("core-js/modules/es.object.get-own-property-descriptors");

require("core-js/modules/es.object.get-own-property-names");

require("core-js/modules/es.object.get-prototype-of");

require("core-js/modules/es.object.is");

require("core-js/modules/es.object.is-extensible");

require("core-js/modules/es.object.is-frozen");

require("core-js/modules/es.object.is-sealed");

require("core-js/modules/es.object.keys");

require("core-js/modules/es.object.lookup-getter");

require("core-js/modules/es.object.lookup-setter");

require("core-js/modules/es.object.prevent-extensions");

require("core-js/modules/es.object.seal");

require("core-js/modules/es.object.to-string");

require("core-js/modules/es.object.values");

require("core-js/modules/es.promise");

require("core-js/modules/es.promise.finally");

require("core-js/modules/es.reflect.apply");

require("core-js/modules/es.reflect.construct");

require("core-js/modules/es.reflect.define-property");

require("core-js/modules/es.reflect.delete-property");

require("core-js/modules/es.reflect.get");

require("core-js/modules/es.reflect.get-own-property-descriptor");

require("core-js/modules/es.reflect.get-prototype-of");

require("core-js/modules/es.reflect.has");

require("core-js/modules/es.reflect.is-extensible");

require("core-js/modules/es.reflect.own-keys");

require("core-js/modules/es.reflect.prevent-extensions");

require("core-js/modules/es.reflect.set");

require("core-js/modules/es.reflect.set-prototype-of");

require("core-js/modules/es.regexp.constructor");

require("core-js/modules/es.regexp.exec");

require("core-js/modules/es.regexp.flags");

require("core-js/modules/es.regexp.to-string");

require("core-js/modules/es.set");

require("core-js/modules/es.string.code-point-at");

require("core-js/modules/es.string.ends-with");

require("core-js/modules/es.string.from-code-point");

require("core-js/modules/es.string.includes");

require("core-js/modules/es.string.iterator");

require("core-js/modules/es.string.match");

require("core-js/modules/es.string.pad-end");

require("core-js/modules/es.string.pad-start");

require("core-js/modules/es.string.raw");

require("core-js/modules/es.string.repeat");

require("core-js/modules/es.string.replace");

require("core-js/modules/es.string.search");

require("core-js/modules/es.string.split");

require("core-js/modules/es.string.starts-with");

require("core-js/modules/es.string.trim");

require("core-js/modules/es.string.trim-end");

require("core-js/modules/es.string.trim-start");

require("core-js/modules/es.string.anchor");

require("core-js/modules/es.string.big");

require("core-js/modules/es.string.blink");

require("core-js/modules/es.string.bold");

require("core-js/modules/es.string.fixed");

require("core-js/modules/es.string.fontcolor");

require("core-js/modules/es.string.fontsize");

require("core-js/modules/es.string.italics");

require("core-js/modules/es.string.link");

require("core-js/modules/es.string.small");

require("core-js/modules/es.string.strike");

require("core-js/modules/es.string.sub");

require("core-js/modules/es.string.sup");

require("core-js/modules/es.typed-array.float32-array");

require("core-js/modules/es.typed-array.float64-array");

require("core-js/modules/es.typed-array.int8-array");

require("core-js/modules/es.typed-array.int16-array");

require("core-js/modules/es.typed-array.int32-array");

require("core-js/modules/es.typed-array.uint8-array");

require("core-js/modules/es.typed-array.uint8-clamped-array");

require("core-js/modules/es.typed-array.uint16-array");

require("core-js/modules/es.typed-array.uint32-array");

require("core-js/modules/es.typed-array.copy-within");

require("core-js/modules/es.typed-array.every");

require("core-js/modules/es.typed-array.fill");

require("core-js/modules/es.typed-array.filter");

require("core-js/modules/es.typed-array.find");

require("core-js/modules/es.typed-array.find-index");

require("core-js/modules/es.typed-array.for-each");

require("core-js/modules/es.typed-array.from");

require("core-js/modules/es.typed-array.includes");

require("core-js/modules/es.typed-array.index-of");

require("core-js/modules/es.typed-array.iterator");

require("core-js/modules/es.typed-array.join");

require("core-js/modules/es.typed-array.last-index-of");

require("core-js/modules/es.typed-array.map");

require("core-js/modules/es.typed-array.of");

require("core-js/modules/es.typed-array.reduce");

require("core-js/modules/es.typed-array.reduce-right");

require("core-js/modules/es.typed-array.reverse");

require("core-js/modules/es.typed-array.set");

require("core-js/modules/es.typed-array.slice");

require("core-js/modules/es.typed-array.some");

require("core-js/modules/es.typed-array.sort");

require("core-js/modules/es.typed-array.subarray");

require("core-js/modules/es.typed-array.to-locale-string");

require("core-js/modules/es.typed-array.to-string");

require("core-js/modules/es.weak-map");

require("core-js/modules/es.weak-set");

require("core-js/modules/web.dom-collections.for-each");

require("core-js/modules/web.dom-collections.iterator");

require("core-js/modules/web.immediate");

require("core-js/modules/web.queue-microtask");

require("core-js/modules/web.url");

require("core-js/modules/web.url.to-json");

require("core-js/modules/web.url-search-params");

require("regenerator-runtime/runtime");

require("./sampleFormHandler");

require("./sample.css");


babelの設定を変えて確認

.babelrc.jstargetsの部分を変えるとトランスパイル・ポリフィル結果が変わる。

例えばie: '11'の部分はIE11でも動作するコードにトランスパイル・ポリフィルする設定であるが、ie: 11の部分をコメントアウトして試してみる。


.babelrc.js (IE11指定をコメントアウト)

module.exports = (api) => {
  api.cache(true);
  const presets = [
    [
      '@babel/preset-env',
      {
        targets: {
          chrome: '79',
          // ie: '11', // <- コメントアウト
          firefox: '72',
          safari: '13',
        },
        useBuiltIns: 'entry',
        corejs: 3,
        debug: true,
      },
    ],
  ];

  const plugins = [];

  return {
    presets,
    plugins,
  };
};

IE11をターゲットから外して maskKey.jsのトランスパイル

npx babel maskKey.js -o output.js

結果

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

// const, アロー関数, デフォルト引数,
const maskKey = (key, padString = '*') => {
  // const
  const sliced = key.slice(-4); // String.prototype.padStart

  return sliced.padStart(key.length, padString);
};

var _default = maskKey;
exports.default = _default;


IE11をターゲットから外して entry.jsのポリフィル

npx babel entry.js -o output.js

結果

"use strict";

require("core-js/modules/es.promise.finally");

require("core-js/modules/es.string.replace");

require("core-js/modules/es.typed-array.float32-array");

require("core-js/modules/es.typed-array.float64-array");

require("core-js/modules/es.typed-array.int8-array");

require("core-js/modules/es.typed-array.int16-array");

require("core-js/modules/es.typed-array.int32-array");

require("core-js/modules/es.typed-array.uint8-array");

require("core-js/modules/es.typed-array.uint8-clamped-array");

require("core-js/modules/es.typed-array.uint16-array");

require("core-js/modules/es.typed-array.uint32-array");

require("core-js/modules/es.typed-array.from");

require("core-js/modules/es.typed-array.of");

require("core-js/modules/web.dom-collections.iterator");

require("core-js/modules/web.immediate");

require("core-js/modules/web.url");

require("core-js/modules/web.url.to-json");

require("core-js/modules/web.url-search-params");

require("./sampleFormHandler");

require("./sample.css");

結果について

  • アロー関数などがfunction(){}の書き方に変わっている
  • polyfillのためのパッケージのrequire文が列挙されている
  • IE11をターゲットから外すとアロー関数などはそのまま
  • IE11をターゲットから外すとpolyfillのためのパッケージのrequire文が減る

webpack


webpackとは

  • bundle, minifyを行うツール
  • 他にもいろいろできる。linterを設定するとか(今回はやらない)
  • 依存関係を解決して一つにまとめるのが目的

webpackの環境構築

次のコマンドを実行してパッケージをインストールする


npm i webpack webpack-cli babel-loader css-loader style-loader

webpackの環境構築

  • webpack.config.babel.js
import path from 'path';

export default (env, args) => {
  const isProduction = args.mode === 'production';
  const devtool = !isProduction && 'inline-source-map';
  const rules = [
    {
      test: /\.js?$/,
      use: ['babel-loader'],
    },
    {
      test: /\.css$/,
      use: ['style-loader', 'css-loader'],
    },
  ];

  const plugins = [];

  return {
    devtool,
    entry: './entry.js',
    output: {
      path: path.join(__dirname, './'),
      filename: 'bundle.js',
    },
    module: { rules },
    plugins,
  };
};

動作確認

minifyする

npx webpack --mode production

生成されるbundle.jsの中身(一部)

!function(t){var e={};function n(r){if(e[r])return e[r]……(省略)

minifyしない

npx webpack --mode development

生成されるbundle.jsの中身(一部)

/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
……(省略)

ブラウザで動作確認

  • index.htmlをブラウザで開く
qiita_babel_webpack_sample.png

おまけ


watch機能

ソースコードの変更を監視、変更があれば都度webpack実行(Ctrl+cで終了)

npx webpack --mode development --watch

npm scriptsの活用

  • package.json"scripts"に以下のように記述追加
  "scripts": {
    "build": "webpack --mode production",
    "build:dev": "webpack --mode development",
    "watch": "webpack --mode development --watch"
  },

npm run watch だけでwatchできる


watchしながら実装変更してみよう

  • 文言を変える
  • cssを変える
  • ロジック変える
  • などなど

まとめ


今回の学習のまとめ

  • transpile,polyfill,bundle,minifyを前提として、ターゲットブラウザに依存せずにES6+で、ファイルを分割してプログラミングする今時の書き方が理解できた
  • Babel で transpile, polyfill できるようになった
  • Webpackでbundle,minify できるようになった

終わり

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?