はじめに
第1章 環境構築をしよう
第2章 シンプルなカメラアプリを作ろう
第3章 カメラの設定を変えよう
第4章 EXIFをつけよう
第5章 コンポーネントを整理しよう
第6章 フィルターを実装しよう
第7章 QR コードリーダーを作る
第8章 アニメーションGIFを作ろう
第9章 PWAとして配信しよう
付録A ES Modulesで開発しよう
あとがき
ここ数年で、ブラウザー上でできることは格段に増えました。例えば、Web Paymentsを使えば決済が、Web USBやWeb MIDIを使えばデバイスを操作することもできます。さらに、PWA(Progressive Web Apps)といったWebアプリにより多くの機能を持たせたものが登場しています。
そういったブラウザー機能が発展する始まり、最初の大きな挑戦といえるのはWebRTCでしょう。WebRTCがW3Cのワーキングドラフトに上がったのは、2011年10月1のことでした。それから2年の歳月を経た2013年の夏に、Google ChromeとFirefox上でビデオカメラとマイクが使えるようになりました。Safariについては、2017年にWebRTCがようやく導入されました。
長い間、ブラウザーで使える機能の中でも特殊な存在であったカメラ機能ですが、これを使ったWebアプリはさほど普及していないように思われます。昨今のPWAの普及と流行で、豊富な機能を使ったWebアプリを作る土壌が整っているにもかかわらず、作られるのは「ブログサイトをホーム画面に追加する」といった小さなものばかりです。Webアプリの可能性は、ブログサイトをちょっと便利にする程度ではないはずです。そろそろ本当のWebアプリを作ってみる時期ではないでしょうか。
本書ではカメラアプリを例に、Webアプリを最初から作っていきます。この本を通じて、Webアプリの可能性を体感してください。
本書は、カメラアプリをWebアプリとして作るハンズオン形式となっています。手を動かしながら読み終えると、カメラアプリが完成しているということです。
ただ、実際にどのようなカメラアプリが完成するかのイメージがないとモチベーションが上がらないと思いますので、ここでその機能を紹介します。
・写真が撮れます
・写真管理しやすいように日時・GPS情報なども付きます
・写真にフィルターをかけられます
・QRコードを読み込むことができます
・アニメーションGIFが撮影できます
・アプリとして配信できます(Google Play Storeにも配信できます)
機能だけを見ると普通のカメラアプリではありますが、一番のポイントはこれらすべてがブラウザーで動くという点です。
実際のアプリは、https://the-camera.netlify.comで体験できます(※カメラ機能のあるスマートフォンなどの端末でアクセスしてください)。
本書では、ブラウザーでカメラアプリを作るに当たって必要な知識を広く浅く学べます。具体的には、次のような知識を盛り込んでいます。
・一般的なWebフロントエンド開発環境
・ブラウザーからカメラを使う方法
・EXIFの基本知識
・WebGLを使った画像フィルター
・WebWorkerを使ったスレッド処理
・新しいAPIによる顔認識・バーコード読み込み
・RustによるWebAssembly開発
・PWAによるキャッシュ処理とアプリ配信
・ES Modulesによるバンドルレスな開発
明日すぐに役立つような知識ではないかもしれませんが、今Webブラウザーで何ができるのかが体感できます。勉強のために読むというよりは、娯楽として楽しんでいただけたら幸いです。
本書は、9つの章とひとつの付録で構成されています。ハンズオン形式のため、順番に読んでいくことを推奨します。
・1章 環境構築をしよう
webpackを使った一般的な開発環境を構築します。ビルド環境だけでなく、Prettierなどを使った綺麗なコードを書くための設定も含まれています。
・2章 シンプルなカメラアプリを作ろう
写真を撮るだけの最低限な機能を持ったカメラアプリを作ります。ここでは、ブラウザーからカメラを使う方法を解説します。また、CSS Gridを使った画面設計についても学習します。
・3章 カメラの設定を変えよう
カメラの解像度やズームの倍率を変える方法を説明します。スマートフォンではほぼ当たり前に搭載されている、インカメラとの切り替えも扱います。ここまで進むと、ようやく普通のカメラアプリに近くなります。
・4章 EXIFでメタデータをつけよう
写真の管理にはメタデータが必須です。この章では簡単なEXIFの仕様と、データにEXIF情報を含める方法を紹介します。また、メタデータに含めるためにスマートフォンの傾きや位置情報を取得する方法も解説します。
・5章 画像フィルターを実装しよう
カメラアプリによく見られる、画像フィルターを実装します。JavaScriptで実装する方法も解説しますが、この方法では処理時間が長くなってしまう問題があります。そこで、画像処理に適したWebGLを使ってフィルター処理の高速化を図ります。
・6章 コンポーネントを整理しよう
Webアプリを作っていると、似た機能を持つパーツがいくつか出てきます。この章では、前章までに作ったコンポーネントから機能ごとに切り分ける例を紹介します。
・7章 QRコードリーダーをつけよう
便利なカメラアプリに必要な機能は何でしょうか。例えばQRコードリーダー機能は、使う場面は多くないものの搭載されていると地味に役立ちます。この章では、新しいAPIを活用してQRコードリーダーを実装します。また、読み込んだ文字をWeb Share APIで他のアプリへ送る機能も実装します。
・8章 アニメーションGIFを作ろう
カメラで撮影できるものは、静止画だけではありません。今回は、昔懐かしいアニメーションGIFで動画を保存できるようにします。WebAssemblyを使うことで、高速にアニメーションGIFを作ることができます。
・9章 PWAとして配信しよう
アプリを作ったら、もちろん公開したくなると思います。ここまではブラウザーで開いていましたが、PWAにすることでよりアプリらしく体裁を整えていきます。最終的には、Trusted Web Activityを使ったGoogle Play Storeへの掲載まで行います。
・付録 ES Modulesでバンドルレスに開発しよう
ES Modulesがブラウザーに実装されたため、webpackなしでもほぼ同じコードが動くようになりました。付録では、ES Modulesで動かすためにはどういった処理が必要かを解説します。Template literalsを活用すれば、JavaScript内にJSXやCSSを埋め込むことができます。まだ制約は多いものの、将来的にはバンドル処理から開放されるかもしれません。
本書は、簡単なWebアプリを作ったことのあるフロントエンドエンジニアを対象にしています。また、フレームワークとしてReactを採用しているため、JSX記法やReactのライフサイクルについての知識を必要とします。はじめてWebアプリの開発に挑む方は、本書を読む前にReact公式のチュートリアル2や、その他の入門書を読んでおくことを推奨します。
本書では、ECMAScript 2018に準拠したコードを記述しています。ただし、Reactのイベントハンドラを書くときの手間を省くため、策定中の仕様であるClass field declarations proposal3を一部採用しています。
本書に書かれているコードは、次の環境での動作確認を行っています。ただし、仕様策定前のAPIなどを利用しているため、今後のアップデートによっては動作しない場合があります。
・Node.js v10.15.1
・Rust v1.32.0
・Visual Studio Code v1.31.1
・Android Studio v3.3.1
・Android Chrome v72.0.3626.105
・Android Firefox v65.0.1
・iOS Safari v12.1
これから表記するコードは、同一ファイルであれば変化のある部分のみを抜粋して掲載しています。例えば、次のようなクラスがあるとします。
class Test {
constructor() {
this.number = 10;
}
}
その後に、Testクラスへalertメソッドを追加する場合は、次のように表記します。
class Test {
alert() {
window.alert(this.number);
}
}
実際のコードは、前述のコードと合わせて次のようになります。
class Test {
constructor() {
this.number = 10;
}
alert() {
window.alert(this.number);
}
}
削除する部分は適宜併記しますが、自分のコードと照らし合わせながら確認しつつ読み進めてください。
この本に関するご意見・ご質問がありましたら、Twitter: @3846masaまでご連絡ください。また、ご感想等はTwitterのハッシュタグ#CameraWebAppをつけて呟いていただけると嬉しく思います。
本書に記載された内容は、情報の提供のみを目的としています。したがって、本書を用いた開発、製作、運用は、必ずご自身の責任と判断によって行ってください。これらの情報による開発、製作、運用の結果について、著者はいかなる責任も負いません。
本書に記載されている会社名、製品名などは、一般に各社の登録商標または商標、商品名です。会社名、製品名については、本文中では©、®、™マークなどは表示していません。
本書籍は、技術系同人誌即売会「技術書典5」で頒布されたものを底本としています。
CommonJSの台頭によって、昨今のフロントエンド開発ではコードをモジュール化して読み込ませることが基本となっています。今では、ECMAScriptの仕様としてES Modulesが制定され、ほとんどのモダンブラウザーでモジュールを読み込めるようになりました。
ES Modulesがブラウザーに実装される以前、モジュールの読み込みはNode.jsのrequireがデファクトスタンダートでした。このrequire記法やES Modulesで書かれたコードをブラウザー上で動作させるため、あらかじめ読み込むモジュールをひとつのファイルに結合するツールが普及します。それがバンドラーです。
バンドラーには、CSSなどのJavaScript以外のデータもモジュールとして読み込める機能や、生成されたJavaScriptを最適化する機能を備えたものもあり、ただコードを結合するだけ以上の役割を持ちつつあります。前述の通り、モダンブラウザーではバンドラーがなくてもモジュールを読み込めますが、それ以外の機能の恩恵を考えると、やはりバンドラーはフロントエンド開発に必要不可欠といえるでしょう。
有名なバンドラーには、Browserify1、Rollup2、Parcel3などがあります。なかでも拡張性が高く多機能なwebpack4は多くのユーザーが開発に使っています。今回は、webpackを利用した環境を構築していきましょう。
Node.jsには、パッケージマネージャーとしてnpmが付属していますが、今回はより高速にパッケージをインストールできるyarn5を使います。次のコマンドで、yarnをインストールしましょう。
$ npm install --global yarn
つづいて、webpackをインストールします。webpackをCLIから使うには、webpack-cliが必要です。webpack-dev-server6は、開発用サーバーを立てるために使います。後述するHot Module Replacementなど、開発に役立つ機能も多く搭載しています。
一緒にwebpackのプラグインもいくつかインストールします。
html-webpack-plugin7は、ビルド時にHTMLを生成するwebpackプラグインです。license-webpack-plugin8は、バンドルされるモジュールのライセンス表記をテキストファイルとして書き出します。clean-webpack-plugin9は、ビルド時に指定したファイルを削除するプラグインで、前回の出力結果を消すときに使います。
$ yarn add --dev webpack webpack-cli webpack-dev-server \
html-webpack-plugin license-webpack-plugin clean-webpack-plugin
最低限のwebpackの設定をwebpack.config.jsに書きます。entryが読み込みファイル、output.pathが出力先のフォルダーです。pluginsでは、webpackのプラグインを設定します。今回は、src/index.htmlをベースにHTMLを自動生成するようにhtml-webpack-pluginを設定しています。
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { LicenseWebpackPlugin } = require('license-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
/** @type {import('webpack').Configuration} */
const config = {
entry: path.resolve(__dirname, './src/index'),
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].[hash:8].js',
chunkFilename: '[name].[chunkhash:8].js',
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './src/index.html'),
}),
new LicenseWebpackPlugin({
addBanner: true,
}),
new CleanWebpackPlugin(),
],
};
module.exports = config;
さいごに、Reactでシンプルな"Hello World"コードを書いてみましょう。reactとreact-domをインストールしてから、src/index.htmlとsrc/index.jsを次のように書きます。html-webpack-pluginによって、ビルドしたスクリプトを読み込むための<script>タグは自動で挿入されるため、src/index.htmlには<script>タグを書かないでおきます。
$ yarn add react react-dom
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>The Camera</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
React.createElement('h1', {}, 'Hello World!'),
document.getElementById('app'),
);
ここまでを一度実行してみましょう。npxを使うと、インストールしたパッケージに付属するCLIツールを実行できます。npxからwebpack-dev-serverを起動して、http://localhost:8080にアクセスします。webpack-dev-serverの--modeオプションには、開発モードであるdevelopmentを指定します。図1.1と同じ画面が表示されれば準備完了です。
$ npx webpack-dev-server --mode development
ここまでの内容を一度Gitでcommitしておきましょう。しかし、このままgit addすると、node_modulesにある多くのファイルが無駄にトラッキングされてしまいます。.gitignoreに除外するファイル名を書くことで、トラッキングするファイルを制御できます。しかし、node_modulesのように広く知られている除外ルールはさておき、エラーログであるyarn-error.logなどをすべて把握するのは大変です。
こんなときにはgitignore.io10を使いましょう。gitignore.ioは、その名の通り.gitignoreを作成してくれるサイトです。テンプレートの更新もGitHubで盛んに行われており、例えば他のバンドラーであるParcelの設定などもNode.js用テンプレートに含まれています。
また、APIも公開されているため、wgetやcurlから簡単に読み込むことができます。今回はNode.jsとVisual Studio Code、そしてWindowsとmacOSのテンプレートをあわせて使いましょう。ちなみに、ビルドファイルの出力先として指定した/distなど、ケースによって異なるものは含まれていません。
$ wget -O .gitignore \
https://www.gitignore.io/api/node,visualstudiocode,windows,macos
$ echo "/dist" >> .gitignore
ReactにはJSXという記法があり、React.createElementをHTMLタグのように記述できます。先程のsrc/index.jsをJSXで記述すると次のようになります。
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<h1>Hello World!</h1>,
document.getElementById('app'),
);
JSX記法で書かれたファイルの拡張子として.jsxを使うことがあります。拡張子によって何かが変わることはないため、好きな拡張子で書くと良いでしょう。ただし、標準のwebpackでは.jsx拡張子を認識してくれません。.jsx拡張子を使うために、設定にあるresolve.extensionsへ拡張子を加える必要があります11。本書では設定を簡略化するために、ファイルの拡張子は.jsのまま変えずに書いていきます。
JSX記法は、ECMAScriptとして制定されている記法ではないため、ブラウザーで動かすためには変換する必要があります。この変換のことをトランスパイルと呼びます。今回はトランスパイルツールであるBabelを使います。
まずは、Babel関連のライブラリーをインストールしましょう。@babel/coreがBabel本体で、@babel/preset-reactがJSXをトランスパイルするためのライブラリー群(プリセット)です。babel-loaderは、webpackのローダーの一種です。ローダーとは、モジュールを読み込むときにトランスパイルするライブラリーを指します。
$ yarn add --dev @babel/core @babel/preset-react babel-loader
Babelの設定は、.babelrcファイルに書くのが一般的です。他にもwebpack.config.jsに含める方法があります。次のようにしてBabelの設定を書きます。
{
"presets": ["@babel/preset-react"]
}
さいごにローダーの設定をwebpack.config.jsのmodule.rulesに書きます。testには、適用するファイル名に合致する正規表現を書き、useには、使用するローダーを与えます。追記した部分だけを抜粋すると、次のようになります。
const config = {
module: {
rules: [
{
test: /\.m?js/,
exclude: /node_modules/,
use: ['babel-loader'],
},
],
},
};
ここまでをもう一度実行してみます。うまく設定できていれば、先程と同じ画面が表示されます。
ブラウザー上のJavaScriptは、ECMAScriptという仕様に沿って実装されています。しかし、ECMAScriptで制定されていても、ブラウザーではまだ実装されていないことが多々あります。そうした新しい記法も、Babelを使ってトランスパイルすることで多くのブラウザーで動くようにできます。今回は、ECMAScriptの最新仕様と、現在ECMAScriptで提案されているClass field declarationsを使えるように設定してみます。
core-jsには、足りない実装を補うためのコードであるPolyfillが入っています。@babel/preset-envの設定で、useBuiltInsをusageにしたとき、core-jsから必要に応じてPolyfillを読み込んでくれます。
webpackではES Modulesで読み込まれるモジュールのうち、使われないモジュールをバンドルしないようにする最適化機能"Tree Shaking"が実装されています。"Tree Shaking"を有効にするために、modulesをfalseに設定して、ES Modulesをトランスパイルしないようにします。
$ yarn add core-js
$ yarn add --dev @babel/preset-env \
@babel/plugin-syntax-dynamic-import \
@babel/plugin-proposal-class-properties
{
"presets": [
"@babel/preset-react",
[
"@babel/preset-env",
{
"modules": false,
"useBuiltIns": "usage"
}
]
],
"plugins": [
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-proposal-class-properties"
]
}
コードに変更を加えたとき、その変更を確認するためにはブラウザーで更新を行う必要があります。しかし開発を続けていくと、変更するたびにブラウザーで更新することが面倒になってきます。そこで、Hot Module Replacement(HMR)の仕組みを使って、自動でコードの変更を読み込むようにしてみましょう。
ここではReactのHMRとして有名なライブラリーであるreact-hot-loader12を使います。先程のsrc/index.jsから、コンポーネント部分をsrc/App.jsに分けて、HMRが効くようにします。src/App.jsのコンポーネントをreact-hot-loaderの監視下にするため、hot関数でコンポーネントをくるみます。src/index.jsのほうは、exportされた<App>コンポーネントを読み込みます。
$ yarn add --dev react-hot-loader
import React from 'react';
import { hot } from 'react-hot-loader/root';
const App = () => <h1>Hello World!</h1>;
export default hot(App);
import React from 'react';
import ReactDOM from 'react-dom';
import App from '~/App';
ReactDOM.render(<App />, document.getElementById('app'));
このとき、importでローカルのパスを指定しますが、webpackのalias機能でsrc/以下のファイルを~/から参照できるようにしておきます。そうすることで、あとでフォルダー構造を変えたくなったときに、importのパスを書き換える手間が少なくなります。さいごに.babelrcのpluginsにreact-hot-loaderの設定を追記します。
const config = {
resolve: {
alias: {
'~': path.resolve(__dirname, './src'),
},
},
};
{
"plugins": [
"@babel/plugin-proposal-class-properties",
"react-hot-loader/babel"
]
}
設定を終えたら、webpack-dev-serverを再起動します。このとき--hotオプションをつけることで、HMRを有効化できます13。起動した後に、ページを開きながらsrc/App.jsのテキストを変更してみましょう。自動で変更が反映されるようになっていれば、HMRが動いています。
$ npx webpack-dev-server --hot --mode development
Visual Studio Code(VSCode)を使っている方は、jsconfig.jsonを作っておくとパス補完やコード補完が効くようになって便利です14。今回のように~/などのAliasが張られている環境でも、jsconfig.jsonに書いておくと自動でファイルパスを探してくれます。
最近では、パス補完やコード補完のほかにファイルの移動に伴うパスの書き換えなどもしてくれるため、VSCodeを使っているならば必ず設定しておきましょう。
{
"compilerOptions": {
// Detect unused variables
"noUnusedLocals": true,
"noUnusedParameters": true,
// Use dom, esnext types
"lib": ["dom", "esnext"],
// Enable JSX
"jsx": "preserve",
// Aliases
"baseUrl": ".",
"paths": {
"~/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist"]
}