Parcel のどうということはない小技集

## 概要

Parcel で LP のような簡単なページを作る際に役立つかもしれない TIPS をまとめてみました。
[私のサイトのトップページ](https://mimonelu.net/)などはまさに Parcel で作ったのですが、機能もへったくれもないので、そういうのを求めている方向けではありません。

```shell
$ npm i --save-dev parcel-bundler # `parcel` じゃないよ
```

## 小技

### index.html を一階層上に移動する

.html ファイルだけはフォルダ構造を保って出力されるようになったっぽいので、以下は読む必要ありません。
~~Parcel は何でもかんでも dist フォルダにファイルをビルドしちゃいますが、 index.html だけ上の階層に移動したい、というケースがあるかもしれません。私はありました。
しかし Parcel にそんな気の利いた機能はないので、 `mv` コマンドで移動する必要があります。
が、そのままだと index.html 内部の相対パスが壊れてしまうため、ビルドする際には `--public-url` オプションを付ける必要もあります。
例えば、 `/top/index.html` を `/index.html` に移動したい場合は以下のようになるかと。~~

```json
"scripts": {
  "dev": "parcel ./src/*.html --no-cache --no-source-maps --out-dir dist/top --target browser",
  "build": "parcel build ./src/*.html --no-cache --no-source-maps --out-dir dist/top --public-url ./top --target browser && mv dist/top/index.html dist/index.html"
}
```

### `http://localhost:1234/` が 404

`parcel ./src/index.html` とした場合は `http://localhost:1234/` でページが表示されます。
が、 `parcel ./src/*.html` とワイルドカードを使った場合は `http://localhost:1234/index.html` と明示的にファイル名を指定しなければページは表示されません。

### 古いビルドファイルを削除する

Parcel でビルドすると `top.44a62113.js, top.9666360a.js, ...` というように古いファイルが際限なく残り続けます。
これはもちろんキャッシュ対策であって正しい挙動なんですが、いやそんな残さんでええよ？という時は `rm` コマンドを npm-scripts の `prebuild` かビルドコマンドの前に書くと良さげです。

```json
"scripts": {
  "build": "rm dist/top/* ; parcel build ..."
}
```

なお、[こちら](https://github.com/parcel-bundler/parcel/issues/1234)によると `parcel-plugin-clean-dist` とか `parcel-plugin-clean-easy` というプラグインもあるようですが、使う必要あるんでしょうか？わかりません。

### Sass を使う

どこにでもある情報ですが、 `.scss` ファイルを JavaScript で import して Parcel を走らせれば自動的に `sass` パッケージが読み込まれ、 CSS に変換されます。
（ Parcel には Auto-install 機能があるので、手動で npm install するのは避けた方が良いかな、と。）

```
import './index.scss'
```

ちなみに Normalize CSS や Reset CSS も JavaScript で import すれば勝手にインストールされます。

```javascript
import 'normalize.css'
import 'reset-css'
```

上二つの Sass 版を使いたい場合は `npm i --save normalize-scss reset-css` してから Sass で import する必要がある、のかな？ Parcel 関係ないけど。

```css
@import '../node_modules/normalize-scss/sass/normalize';
@include normalize();
@import '../node_modules/reset-css/sass/reset';
```

### Autoprefixer を使う

まず、 package.json と同階層に `postcss.config.js` を作り、以下のようなコードを書きます。

```javascript
module.exports = {
  plugins: {
    autoprefixer: {}
  }
}
```

次に package.json に browserlist を追記すれば、 `autoprefixer` がインストールされ、プリフィクスが付与されます。 `display: flex;` なんかで試すと良くわかりますね。

```json
"browserslist": [
  "cover 99.5%"
]
```

なお、 postcss.config.js に browserlist を書くと、下記のようなエラーが表示されるようになりました。

```
Replace Autoprefixer browsers option to Browserslist config.
Use browserslist key in package.json or .browserslistrc file.
```

### Babel を使う

やっぱりどこにでもある情報ですが、 package.json と同階層に `.babelrc` を作り、以下のようなコードを書けば `babel-core` と（上記の例では） `babel-preset-env` がインストールされるので楽ちんです。

```json
{
  "presets": [
    [
      "env", {
        "targets": {
          "browsers": [ "> 0.1%" ]
        }
      }
    ]
  ]
}
```

なお、 `async` `await` などを使って `Uncaught ReferenceError: regeneratorRuntime is not defined` というエラーが表示された場合は、 .babelrc にプラグインを追記すると良いようです。

```json
{
  "presets": [ ... ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime", {
        "corejs": 2
      }
    ]
  ]
}
```

あるいは Polyfill を import します。

```javascript
import 'babel-polyfill'
```

[他にも対応策はいくつかありますね。](https://github.com/parcel-bundler/parcel/issues/871)

### テンプレートエンジンを使う

#### Pug (Jade)

お察しの通り、拡張子を `.pug` にすれば自動的に `pug` がインストールされ、使えるようになります。

#### Vue

こちらも npm install は不要です。
JavaScript で `import Vue from 'vue'` などと書けば `vue` と関連プラグインがインストールされます。

```javascript
import Vue from 'vue'
import Footer from './footer.vue'

new Vue({
  el: '#footer',
  render: h => h(Footer)
})
```

追記: Vue を使うなら Parcel より VueCLI を使った方が絶対良いです。

### Google Analytics を（ Vue で）使う

テンプレートエンジンを使わない場合は直に書けば良いのですが、 Vue を使っている場合は `vue-analytics` を使うと便利です。これも Parcel 関係ありませんが…。
`npm i --save vue-analytics` で手動インストール後、任意の箇所に下記コードを記述するだけ。

```javascript
import Vue from 'vue'
import VueAnalytics from 'vue-analytics'

Vue.use(VueAnalytics, { id: 'UA-XXXXXXXXXX-X' }) // 🔥 TODO: ここ変えて
Vue.$ga.page()
```

ページが複数ある場合は `Vue.$ga.page({ page: window.location.pathname })` などとすると良いでしょう。
Vue Router も設定できます…が、 SPA で Parcel を使うのが適切かどうか怪しいので割愛します。

### 勝手にインストールしてくれるけど、勝手にアンインストールはしてくれない

あるパッケージが不要になった際は、忘れずに `npm uninstall` しましょう。
とは言え Babel や Vue のように、一回の `import` で 2 つも 3 つもパッケージをインストールされると管理しきれなくなるので、折を見て棚卸しをするべきかもしれませんね。

### ファイルパスをスクリプト内で読み込む

JavaScript 内から画像ファイルなどを直接読み込む際は、 Parcel によって変換されたファイルパスを参照する必要があります。
しかしキャッシュバスター付きのファイルパスは予測できません。そこで `import` を使い、ファイルパスを取得します。
例えば、「 `./images` フォルダ内の全ファイルを画像としてひとつずつ読み込む」コードは以下のようになるでしょう。

```javascript
import 'babel-polyfill'
import images from './images/*.*'

const main = async () => {
  console.time()
  await loadAllImages()
  console.timeEnd()
}

const loadAllImages = async () => {
  for (let imageName in images) {
    for (let extensionName in images[imageName]) {
      const filePath = images[imageName][extensionName]
      const imageEvent = await loadImage(filePath)
      // ここで何かする
      console.log(filePath)
    }
  }
}

const loadImage = filePath =>
  new Promise((resolve, reject) => {
    const image = new Image()
    image.onload = resolve
    image.onerror = reject
    image.src = filePath
  })

main()
```

もちろん `import images from './images/*.png'` というように拡張子を明示することも可能ですが、拡張子を明示した場合としない場合とで返されるオブジェクトの構造が異なることに注意してください。

## おわりに

Parcel は便利なんですが、かゆいところに手が届かなくてやきもきしますね。ゼロコンフィグの宿命でしょうけど。
とはいえゼロコンフィグなバンドラという点ではメジャーな代替ツールもないようなので、期待はしています。