ログイン中のQiita Team
ログイン中のチームがありません

Qiita Team にログイン
コミュニティ
OrganizationイベントアドベントカレンダーQiitadon (β)
サービス
Qiita JobsQiita ZineQiita Blog
CSS
JavaScript
SPA
Vue.js
React
297
どのような問題がありますか?
  • コンポーネントベースの画面構築
  • スタイルのスコープをコンポーネントに閉じる
  • Sass(SCSS)
  • スタイルのスコープ管理
  • Sassのコンパイル
  • Componentクラスの作成
  • ページ遷移とルーティング
  • フレックスボックスを使おう
  • webpackを使う
  • JavaScriptモジュールのバンドル化
  • Babel(ES6をES5に変換する)
  • IEは死にますか?
  • JavaScriptの圧縮
  • Apacheで圧縮ファイルを配信する
  • ローカル環境でプログラムを動かす
  • ローカル環境ではwebpack不要
  • WEBサーバーは何を使うべきか
  • SPAのエントリーポイントを分割する
  • CORSの設定
  • SPAのSEO
  • OGP(Open Graph Protocol)の設定
  • おわりに
  • VueもReactもやったことないのでVanilla JSでやってみたSPA

    はじめに

    まずは宣伝です。
    このたび保育園を地図から探せる 保育園マップ というサービスを作りました。

    Vanilla JSのSPAで作ったのですが、思いの外色んなことをやる必要があったので、制作過程で得た知見をこの記事にまとめました。
    網羅的に書いたので長いですが、一つ一つのトピックはそれ程長くないので、興味があるところだけ読んでもらってもいいと思います。

    Vanilla JS & SPAとは?

    Vanilla(バニラ) JSというのは何もフレームワークを使っていない素のJavaScriptのことです。
    ただのJavaScriptなんですが、ジョークでフレームワーク風の公式サイト?っぽいものがあったりします。

    SPAというのはシングルページアプリケーションの略です。
    ページごとにHTMLを用意するのではなく、1つのHTMLの中でJavaScriptにより表示内容を切り替えるアプリケーションです。

    サンプルアプリケーション

    先程紹介した保育園マップはソースを公開できないので、参考のため別途簡単なサンプルアプリケーションを作りました。
    記事に書いてるコードは基本的にサンプルに入ってるので興味があればご覧ください。

    ReactとVue.js

    npmのダウンロード数などを見ると、最近WEBフロント開発のフレームワークではReactとVueが流行りのようです。
    どっちもやったことはないんですが、今WEBフロントの開発をやるならこれらを使えばよく、わざわざバニラでやる必要はありません。

    ちなみにReactとVueどちらがいいかというと、私はどちらでもいいと思います(笑)

    いや、それにはちゃんと理由があって、React、Vueともにドキュメントを読んだり、ネットの評判を見たり、チュートリアルを触ったり、小さなサンプルを作ってみたりしたのですが、その程度の知識では甲乙つけられませんでした。
    React vs Vueに白黒つける記事もいくつか見かけましたが、それらの記事が正しいか判断することも、知識の少なさから難しいと感じました。
    なので適当に選んで、とりあえずやってみるのが良いのかなと。

    今回はVanilla JSなのでどちらも使いませんが、ReactとVueはこの後も比較対象として何度か話題に出てきます。

    不人気なAngular

    一時期はWEBフロントのフレームワークと言えば、AngularとReactのツートップという印象でしたが、npmパッケージのダウンロード数などを見ると、今ではReactに大きく差をつけられたように見えます。

    Angularは昔少し使ったことがありますが(初代AngularJSとAngular2)、正直もう使いたいと思いません。

    Angularの嫌なところは独自ルールが多く、覚えるのに時間がかかることです。
    特に私はWEBフロント開発をあまりやらないので、フレームワークを覚えても次やるときは忘れていたり、仕様が変わっていたり、フレームワーク自体オワコン化してる可能性もあるので、なるべく学習に時間をかけたくありません。

    React、Vueも当然覚えなければならないことは色々あると思いますが、Angularほど学習コストは高くないように思えました。

    TypeScriptは良いものだが

    私は静的型付け言語ばかりやってきたのもあり、JavaScriptを書いていると型が欲しいとよく思います。
    同じように思う人は多いようで、最近Webフロントの開発では静的型付け言語であるTypeScriptを使うケースが増えています。

    TypeScriptは良い言語だと思いますが、今回使うのはやめました。
    TypeScriptを使わないのにはいくつか理由がありますが、一番は開発環境を整えるのが面倒だからです。

    TypeScriptを使う場合、まずビルド(トランスパイル)環境を作る必要があります。
    また、TypeScriptはライブラリなどの外部APIを使うにあたり、型定義ファイルを用意しないといけないのが手間です。
    一応型を厳密にせず、型定義ファイルなしでやる方法もありますが、それだとどうも負けた感じがしてしまいます(笑)

    TypeScriptを避ける他の理由に、ビルドに時間がかかるというのもあります。
    プログラムが小さいうちは気になりませんが、以前TypeScriptを使ったときはストレスを感じるレベルでビルドに時間がかかっていました。

    あと、最近のIDE(WebStormやVSCode)はJSDocを書くと型チェックやコード補完をしてくれるので、JavaScriptのままでも静的型付け言語に似たメリットを受けることができます。

    まあ、否定的な意見ばかり書きましたが、良い言語だとは思うので、使う理由があれば避けることはないかと思います。

    jQueryは恥だが役に立つ

    15年程前、初めてjQueryを使ったときはブラウザ毎の挙動の違いを吸収できることと、CSSセレクター形式で要素を取得できることに感動しました。
    しかし今では、ブラウザ間の挙動の違いはだいぶ少なくなり、CSSセレクターによる要素取得もJavaScript標準のAPIでできます。

    ではjQueryはもういらないのかと言えば、そんなことはないと思います。
    感動するほどではないけれど、jQueryはまだ現役で役に立つツールです。
    特にAjaxをちょこっと使いたい場合など、素のJavaScriptよりかなり楽に書けます。
    ダウンロードサイズも今のネット環境では大したことはないので、とりあえず入れておいてもさほど害はありません。

    それならバニラよりjQuery使った方がいいじゃん、って話になるのですが、jQueryにもデメリットはあります。
    私が思うjQueryの一番のデメリットは、標準APIとjQueryで2つのやり方が混在してしまうことです。

    例えば、jQueryには配列をループさせるjQuery.eachという関数がありますが、現代のJavaScript(ES6)は同じことが標準のArray.forEach関数でできます。
    そのように複数のやり方が混在すると、プログラムが統一感を失い読みづらくなってしまいがちですし、標準関数でできるならそっちに統一したいですよね。

    そのようなデメリットもあり、今回作ったSPAではjQueryを使わなかったのですが、バニラより楽にはなるので、VueやReactなどのモダンフレームワークを使わないのであれば、jQueryを採用するのは今でもアリかと思います。

    フレームワークのファイルサイズ

    フレームワークを使わずバニラでやった1つの理由が、バニラならフレームワークが不要な分ダウンロードが速いかな、という考えでしたが、実はそこまででもないということが分かりました。

    VueやReact本体のファイルサイズは小さいです。
    Vue.js(v2.6.14)のファイルサイズは94KBですが、CDNからダウンロードするとBrotli圧縮がかかっていて35KBです。
    さらに滅多に変更が入るファイルではないので、2度目以降はキャッシュを利用できる可能性も高いです。
    (ReactはVueよりやや大きいですが、そこまで大きな違いはありません)

    35KBは4Gや光回線だけでなく、3G回線でも一瞬でダウンロードできるサイズです。
    例えば、Qiitaのトップページの総ダウンロード量を見てみると、およそ5200KB程度でした。
    これと比べたら35KBは全然大したことありません。

    回線種別 ダウンロード速度(目安) 35KBのダウンロードにかかる時間
    光回線 80Mbps 0.0035秒
    4G回線 20Mbps 0.014秒
    3G回線 3Mbps 0.09秒

    Vanilla JSのメリット

    フレームワークのサイズが大したことないなら、バニラでやるメリットはあるのか?という話になりますが、実際作ってみてバニラの方が優位だった点が1つありました。
    それは動作が速いことです。

    今回作ったSPAの中に1つ、画面内の要素が多すぎて描画に数秒時間がかかるページがありました。
    どれくらい多いかというと、テキストボックス・チェックボックス・プルダウンなどの入力要素が1画面に1万個以上あります
    この画面を高速化したいと思い、試しにVue.jsで同じようなページを作ってみたのですが、比較するとVanilla JSの方が速かったです。

    仮想DOMは速いみたいな記事を目にすることがあり、VueやReactはなんとなく速いイメージを持っていたのですが、よく考えてみるとVueやReactだって最終的には画面描画のためにリアルDOMを操作するわけで、それなら必要最小限のリアルDOMをピンポイントに操作するバニラの方が速いはずです

    しかし、一般的なWEBアプリケーションでそこまでシビアなパフォーマンスが求められることは少ないと思うので、パフォーマンスを理由にフレームワークを使うことをそれほど躊躇する必要はないとも思います。

    エディタ・IDEの準備

    前置きが長くなりましたが、ここから具体的な実装周りの話に入ります。

    まずはエディタ・IDEを用意します。
    好きなものがあればそれを使えばいいですが、特になければWebStormがお勧めです。
    有料(初年度14,900円)ですが値段分の価値はあると思います。

    どうしても無料が良ければ、VSCodeあたりがいいんじゃないかと思います。
    WebStormもVScodeもWindows、Macどちらでも使うことができます。

    Node.jsのインストール

    次に、Node.jsをインストールします。
    最近のWEBフロント開発ではたいていNode.jsのインストールが必要になります。

    Node.jsのインストール方法は色々あります。
    公式サイトからダウンロードしてインストールするのが一番簡単ですが、長い目で見れば複数バージョンのNode.jsを切り替えられるようにするツールを使うのがお勧めです。
    私はNodebrewというツール(macOS用)を使ってNode.jsをインストールしています。
    使ったことはありませんが、Windowsにもnvm-windowsなど同じようなツールがあるようです。

    NPMとは

    前述のNode.jsをインストールした理由は、実はNode.jsそのものを使いたいわけではなく、中に含まれるNPMというパッケージ管理ツールを使うためです。

    NPMのリポジトリには開発に役立つ様々なパッケージ(ツールやライブラリのようなもの)が登録されていて、package.jsonファイルに必要なものを記載することで、それらをダウンロードして使えるようになります(後述)。

    package.json でパッケージをダウンロードする

    プロジェクトのルートディレクトリに package.json ファイルを作成します。
    前の項でも書きましたが、package.json にはプロジェクトで使うNPMのパッケージ(ライブラリ)を記載します。
    以下のように、devDependencies の中に必要なパッケージの名前とバージョンをセットで書いていきます。

    package.json
    {
      "devDependencies": {
        "@babel/core": "7.12.10",
        "@babel/preset-env": "7.12.11",
        "babel-loader": "8.2.2",
        "compression-webpack-plugin": "9.0.0",
        "http-server": "13.0.2",
        "webpack": "5.55.1",
        "webpack-cli": "4.8.0",
        "sass": "1.42.1"
      }
    }
    
    

    次に、package.jsonのあるディレクトリで以下のコマンドを実行すると、node_modules というディレクトリの中に記載したライブラリがダウンロードされます。

    npmパッケージのインストール
    npm install
    

    package.json にはdevDependencies以外にも色々なことが記載できますが、devDependenciesだけでも動くのでここでは説明は割愛します。
    (repositoryとlicenseがないと警告が出ますが動作に問題はありません)

    各パッケージの概要

    package.json に記載したパッケージ達の概要を説明します。
    それぞれ詳細は後ほど説明します。

    Babel関連

    @babel/core:Babel本体
    @babel/preset-env:Babelの設定のプリセット群

    webpack関連

    webpack:webpack本体
    webpack-cli:webpackのコマンドラインツール
    compression-webpack-plugin: データ圧縮するためのWebPackプラグイン
    babel-loader:webpackでBabelを使うためのツール

    その他

    http-server:動作確認のために使うローカルサーバー
    sass:Sassのコンパイラー

    npmスクリプトの定義

    npm install でインストールしたパッケージに実行可能なコマンドがある場合、node_modules/.binの中に配置されます。
    例えば http-server ライブラリであれば、以下のように node_modules/.bin/http-server を叩いてサーバーを起動することができます。

    8000番ポートでローカルサーバーを起動
     node_modules/.bin/http-server -p 8000
    

    しかしnpmでインストールしたツールは、package.jsonに記載したnpmスクリプトから実行するのが楽です。
    npmスクリプトは任意のコマンド(シェルスクリプト)に名前をつけて、その名前で実行できるようにするものですが、npmスクリプトでは /node_modules/.binが自動でパスに追加されるので、より簡潔にコマンドを書くことができます。

    npmスクリプトの定義
    "scripts": {
      "serve": "http-server -p 8000"
    }
    

    例えばこのように記載すると、npm run serve と打って http-server -p 8000 を実行できます。
    ビルドやサーバーの起動など、開発中によく実行するタスクのコマンドをNPMスクリプトとして登録しておくと便利です。

    Gulpの功罪

    npmスクリプトはシェルスクリプトなので、複雑なタスクを実行するのにはあまり向いていません。
    より複雑なタスクを実行させたい場合は、JavaScriptでタスクを書けるGulp が便利です。

    ただしGulpにはNodeモジュール(NPMパッケージ)を実行するのにGulp用のプラグインが必要というデメリットがあります。
    例えばBabelを使いたい場合、npmスクリプトならただBabelを実行するだけですが、GulpからBabelを実行するにはBabel本体に加えて gulp-babel というGulp用のプラグインが必要になります。

    そしてこのプラグインがよく壊れます😇
    Babelがアップデートしたけど、gulp-babelが対応してないから使えない、みたいなことが起こります。
    そういった理由で世の中には脱Gulpの流れもあったりします。

    基本的なタスクはnpmスクリプトでできるので、これからWEBフロント開発を始めるなら、まずはGulpなしでやってみるのが良いかもしれません。

    HTMLの作成

    JavaScriptで画面を作るSPAと言えどもHTMLは必要になるので、htdocsというディレクトリを作って、HTMLファイルを作ります。
    JavaScriptで画面を構築するので、bodyタグの中は空っぽです。

    index.html
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>バニラSPA</title>
        <meta name="viewport" content="width=device-width">
        <link rel="icon" href="/img/favicon.ico">
        <link rel="stylesheet" href="/css/destyle.css">
        <link rel="stylesheet" href="/css/application.css">
        <link rel="apple-touch-icon" href="/img/touch-icon.png" sizes="192x192">
        <link rel="shortcut icon" href="/img/touch-icon.png">
        <script defer src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
        <script defer type="module" src="/src/application.js"></script>
    </head>
    <body></body>
    </html>
    

    SPA(シングルページアプリケーション)のシングルページ というのはHTMLが1つという意味です。
    SPAでもHTMLを複数作って機能を分割する場合もありますが、サンプルアプリケーションでは1つのHTMLだけで完結させています。

    以下でHTMLの内容について解説していきます。

    ファビコンの設定

    ファビコンの設定
    <link rel="icon" href="/img/favicon.ico">
    

    ファビコンというのは、ブラウザのタブなどに表示されるサイトのアイコンのことです。
    最近のブラウザはPNG画像なども使えますが、対応していないブラウザもあるので ico 形式のファイルにするのが一般的です。

    ico ファイルは1つのファイルの中に、複数サイズの画像を含むことができます。
    特にこだわりがなければ大きな画像を一つ作って、適当なWEBツールでico形式に変換するのが簡単です。
    色々なサイズを含められますが、16×16、32×32の2種類を含めておけば概ね問題ないんじゃないかと思います。

    また、スマホでホーム画面にショートカットを作った場合などのアイコンは、ファビコンとは別に以下のタグで設定する必要があります。

    スマホ向けアイコンの設定
    <!-- iOS向けのアイコン -->
    <link rel="apple-touch-icon" href="/img/touch-icon.png" sizes="192x192">
    <!-- android向けのアイコン -->
    <link rel="shortcut icon" href="/img/touch-icon.png">
    

    この例ではiOS、androidどちらも同じアイコンを設定していますが、別のアイコンを設定することもできます。
    アイコンの表示サイズは端末によって異なりますが、iOSは180x180px、androidは192x192pxが一番大きなサイズなので、どちらも同じ画像を使うなら192x192pxでアイコンを作成するのが良いと思います。

    リセットCSSの適用

    リセットCSSを適用
    <link rel="stylesheet" href="/css/destyle.css">
    

    リセットCSSはブラウザ固有のスタイルをリセットして、ブラウザによる見た目の違いをなくすためのCSSです。
    有名どころだけでも以下のようにたくさんのリセットCSSが世に出ています。

    1. Eric Meyer’s “Reset CSS” 2.0
    2. Yahoo! (YUI 3) Reset CSS
    3. HTML5 Doctor CSS Reset
    4. Normalize.css
    5. ress
    6. sanitize.css
    7. destyle.css

    1、2、3は少し古く、4は5と6で改良されているようなので、お勧めは以下の3つです。

    • ress
    • sanitize.css
    • destyle.css

    ressとsanitize.cssはブラウザ間の差異を消しつも、ある程度のスタイルを与えてくれるのに対して、destyle.css は原則全てのスタイルをリセットするようです。
    一からスタイリングしたかったので、サンプルでは destyle.css を使うことにしました。
    こちらの公式サイトからファイルをダウンロードして、/htdocs/cssディレクトリに配置します。

    ViewPortの設定

    スマホ対応に必要なviewport指定
    <meta name="viewport" content="width=device-width">
    

    スマホ対応する場合はこのような viewport のmetaタグが必要になります。

    viewportはスマホでのみ有効になる機能で(PCブラウザには効かない)、ブラウザの横幅を設定する ことができます。
    例えばブラウザの横幅を300にしたい場合は、以下のように指定します。

    width=300の例
    <meta name="viewport" content="width=300">
    

    ブラウザの横幅を設定するとはどういうことか、イメージがわかないと思いますので、Qiitaトップページのviewportをwidth=300 にしたものと、width=1000 にしたものを、同じスマホで表示したキャプチャーをあげてみます。