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
などで確認すると、dist
にbundle.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.json
のscripts
部分を以下のように変えましょう。
"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の更新
.babelrc
のpresets
にreact
を追加します。これでbabelのトランスパイルでReactのプリセットが利用できるようになります。
{"presets": ["env", "react"]}
webpack.config.jsの更新
続いてwebpack側も更新します。entry
とmodule
の変換対象を.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.js
とservice-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
への直アクセスはエラーにならないようになります。