JavaScriptNode.jsService worker / Workboxwebpack

webpackをちゃんと一から触ってみた(babel / React / Workbox3点セット)

いい加減何がどうなってるのかちゃんと理解して使いたかったので、最近よく使う・これから使いたいなと思っている3点(babel / React + Workbox)込みのサンプルをwebpackでビルドするようにしてみました […]

広告ここから
広告ここまで

いい加減何がどうなってるのかちゃんと理解して使いたかったので、最近よく使う・これから使いたいなと思っている3点(babel / React + Workbox)込みのサンプルをwebpackでビルドするようにしてみました。

プロジェクトのセットアップ

$ mkdir practice
$ cd practice
$ git init
$ npm init -y

webpackを入れる

webpackのGetting Startedを見ながらちゃんとやる。

$ npm i -D webpack webpack-cli

最低限のファイルを用意する

webpackでごにょごにょするJSファイルと、それを実際に使うindex.htmlを用意します。

$ mkdir src
$ touch index.html src/index.js
$ tree -L 1
.
├── index.html
├── node_modules
├── package-lock.json
├── package.json
└── src

src/index.js

const component = () => {
  const element = document.createElement('div')
  element.innerHTML = 'Hello webpack'
  return element
}
document.body.appendChild(component())

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Hello webpack</title>
  </head>
  <body>
    <script src="./src/index.js"></script>
  </body>
</html>

出力を見る

src/index.jsに書いた文字列が表示されるようになりました。ここまではまだwebpackでなにかしているわけではありません。

webpackでバンドルを作る

このままだと、create-react-appとかでSPAを作る時の構成といろいろ違うので、ビルドディレクトリを作って構成をあわせていきます。

distディレクトリを作る

まずはdistディレクトリにindex.htmlを配置するようにしましょう。

$ mkdir dist
$ mv index.html dist/

webpack.config.jsを作る

ここからは、srcに配置されているJSをビルドしてdistに移す必要があります。まずはそのための設定をwebpackでやっていきましょう。

$ touch webpack.config.js
$ vim webpack.config.js

const path = require('path')
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

ここでは、「src/index.jsを読み込んでdist/bundle.jsとしてアウトプットする」という内容がおおよそ書かれています。

npxでwebpackを実行する

それでは実際に先ほど書いたビルドを実行してみましょう。

$ npx webpack --config webpack.config.js 
Hash: b9fb2c380dbf57785037
Version: webpack 4.7.0
Time: 115ms
Built at: 2018-05-06 10:24:08
    Asset       Size  Chunks             Chunk Names
bundle.js  656 bytes       0  [emitted]  main
Entrypoint main = bundle.js
[0] ./src/index.js 170 bytes {0} [built]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

treeなどで確認すると、distbundle.jsが生成されています。

$ tree dist/
dist/
├── bundle.js
└── index.html

dist/index.htmlでbundle.jsを読む

最後にindex.htmlが読み込むJSファイルをbundle.jsに変更しましょう。

<!DOCTYPE html>
<html>
  <head>
    <title>Hello webpack</title>
  </head>
  <body>
    <script src="./bundle.js"></script>
  </body>
</html>

ここまでやると、再び”Hello webpack”が表示されるようになります。

npm scriptでwebpackを実行する

このままnpxで実行する方法でもよいのですが、CI / CDで動かすことなどを考えると、npm run buildのような形で動かしたいなと思います。ということで、package.jsonscripts部分を以下のように変えましょう。

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },

これでnpm run buildするとwebpackが実行されます。

webpackでbabel

arrow functionとかJSXを使いたいとなると、babelは入れておきたくなります。ということで入れましょう。

$ npm install --save-dev babel-preset-env babel-loader babel-core
$ echo '{"presets": ["env"]}' > .babelrc 

続いてwebpack.config.jsにbabelを利用するということを書き足しましょう。

const path = require('path')
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  }
}

module.rulesに「.jsで終わる/node_modules/配下以外のファイルの場合、babel-loaderを読み込みます(意訳)」というコードを足しました。

この状態でnpm run buildが成功すれば、babelの利用準備も整いました。

webpack & babelでReactコンポーネントをビルドする

ここまできたら、Reactコンポーネントのビルドにトライしてみましょう。

# Reactのインストール
$ npm i -S react react-dom

# babelのReact向けプリセットのインストール
$ npm i -D babel-preset-react

# Reactのファイル設置
$ touch src/index.jsx

.babelrcの更新

.babelrcpresetsreactを追加します。これでbabelのトランスパイルでReactのプリセットが利用できるようになります。

{"presets": ["env", "react"]}

webpack.config.jsの更新

続いてwebpack側も更新します。entrymoduleの変換対象を.jsから.jsxに変更するだけですが、忘れるとこの後のReactコードのビルドがされなくなるので要注意です。

const path = require('path')
module.exports = {
  entry: './src/index.jsx',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.jsx$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  }
}

src/index.jsxを作成

そしてReactコンポーネントを用意しましょう。テキストを表示するコンポーネント1つと、実際のDOMにrenderする処理の2つを追加します。

import React from 'react'
import ReactDOM from 'react-dom'

const Hello = () => <h1>Hello React</h1>

ReactDOM.render(
  <Hello/>,
  document.getElementById('root')
);

dist/index.htmlの更新

最後にindex.htmlも更新します。これは上記のReactのrender処理で、id="root"の属性を持つタグが必要になったためです。

<!DOCTYPE html>
<html>
  <head>
    <title>Hello webpack</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="./bundle.js"></script>
  </body>
</html>

ビルドする

ここまでできたら、npm run buildでビルドしてみましょう。正しくビルドできれば、以下のように表示されるはずです。

workboxを入れる

どうせなので+αが欲しいなと思い、workboxをいれてみました。

$ npm i -D workbox-webpack-plugin

webpack.config.jsの更新

webpack.config.jsでserviceWorkerの作成を定義します。

const path = require('path')
const { GenerateSW } = require('workbox-webpack-plugin');

module.exports = {
  entry: './src/index.jsx',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.jsx$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  },
  plugins: [
    new GenerateSW()
  ]
}

この状態でビルドを実行すると、precache-manifest.xxxxx.jsservice-worker.jsがdist配下に生成されます。

$ tree dist/
dist/
├── bundle.js
├── index.html
├── precache-manifest.e19faf6f21b6d812a649eef7a59c2785.js
└── service-worker.js

0 directories, 4 files

index.htmlでserviceWorkerの登録

生成されたファイルを読み込ませましょう。以下の例のように、serviceWorkerが使えるブラウザのみ、service-worker.jsをレジストさせます。

<!DOCTYPE html>
<html>
  <head>
    <title>Hello webpack</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="./bundle.js"></script>
    <script>
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', () => {
        navigator.serviceWorker.register('./service-worker.js')
      })
    }
    </script>
  </body>
</html>

これでserviceWokerの登録と、ビルドしたファイル(bundle.js)をprecacheする処理が追加されました。

現状index.htmlがキャッシュされていませんが、bundle.jsについては一度アクセすることでキャッシュされるようになります。そのためオフライン状態でもbundle.jsへの直アクセスはエラーにならないようになります。

ブックマークや限定記事(予定)など

WP Kyotoサポーター募集中

WordPressやフロントエンドアプリのホスティング、Algolia・AWSなどのサービス利用料を支援する「WP Kyotoサポーター」を募集しています。
月額または年額の有料プランを契約すると、ブックマーク機能などのサポーター限定機能がご利用いただけます。

14日間のトライアルも用意しておりますので、「このサイトよく見るな」という方はぜひご検討ください。

広告ここから
広告ここまで

Related Category posts