pure php+jQuery+scriptベタ貼りで作っていた限界WebアプリをNode.js+Express+Webpackで作り直した

# 限界Webアプリ
数年前に作ったミニwebアプリがありました。
それは所謂アイコンメーカーで、好きなパーツを組み合わせてオリジナルのアイコンを作れるものでした。
まともなマークアップもスクリプトも書けない中、必死で作った思い出のアプリケーションです。

最近、ふとその時のコードを見たら目眩が止まらなくなりました。

### pureすぎるphp
💩phpファイルでそのままルーティングしている。フレームワーク？何それおいしいの？
💩php7環境で動かない古のメソッドも使われていた

### jQuery/scriptタグにベタ打ち
💩jQueryに頼りきったフロント
💩しかもscriptタグでphpにベタ打ちしていた。外部読み込みが存在しない世界。まさに限界集落。
💩JSのお作法も全くわかっていなかったので、動いているかどうかわからないスクリプトタグ・ファイルが多すぎる。胃が爆発する。

あまりにも辛い。
そこでNode.jsの勉強も兼ねてNode.js+Express+Webpack環境でrebornしました。

# 環境構築
とりあえずHello Worldしたい。
Node.jsはインストール済みで、今回は`v10.15.3`で構築しました。
## 初期設定

```bash

$ mkdir reborn_app
$ cd reborn_app
$ npm init

# すべてenter, yes
Press ^C at any time to quit.
package name: (reborn_app)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /path/reborn_app/package.json:

{
  "name": "reborn_app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes) yes
```

### サーバー・テンプレートのモジュール
サーバーサイドは安心と信頼のexpressを使います。
テンプレート言語はhtmlっぽくサクっと書きたい＋触ったことがなかったのでejsを使ってみます。

```bash

$ npm install --save-dev express ejs
```

ejsはviews配下にあるテンプレートをレンダリングするので、
とりあえずindexを作っておきます

```html:./views/index.ejs

<html>
<head>
  <title>hoge</title>
</head>
<body>
  <!-- textはexpressから渡す変数 -->
  <h1><%- text %></h1>
</body>
</html>
```

## expressの設定
expressとテンプレ言語を揃えたのでexpressの設定をしていきます。

```js:server.js
// expressを呼び出す
const express = require('express')
const app = express()
const port = 8080

// view engineをejsにする
app.set('view engine', 'ejs')

// listenメソッドでlocalhost:8080で開けるようにする
app.listen(port, () => {
    console.log(`Server started on: localhost:${port}`)
});

// ルーティング設定
app.get('/', (req, res, next) => {
  const text = 'Hello World!'
  res.render('index', {text: text});
})

```

そしてコマンドラインで以下を叩きます

```sh

$ node server.js
```

https://localhost.8080/
アクセスするとHelloWorldできています。やったぜ。
<img width="400" alt="スクリーンショット 2019-10-09 11.34.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171748/f395577f-5fea-3b9b-90e7-4af49c239154.png">

## webpackでbundleする
素jsをscriptで手作業読み込みは流石にしんどいので
webpackでbundleして読み込みを簡単にします

### webpackの設定
この辺は[公式ドキュメント](https://webpack.js.org/)とほぼ同じです。

```
$ npm install webpack webpack-cli --save-dev
```

```js:webpack.config.js
const path = require('path')

module.exports = {
  entry: './src/index.js', // エントリーポイント。使うmoduleまとめたやつ
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js' // 指定した名前で出力できる
  }
}
```

モジュールはそれぞれ`export default`で書き出し、
エントリーポイントで`import`して発火すればOK。

```js:./src/js/sushi.js
export default () => {
  alert('🍣')
}
```

```js:./src/index.js
// import文を使って sushi.js ファイルを読み込む。
import sushi from './js/sushi.js'

// sushi.jsに定義されたJavaScriptを実行する。
sushi()
```
ただ、webpackコマンドを叩かないといつまでたってもbundleされないので
`package.json`でbuildスクリプトを追加しておきましょう。

```js:package.json
"scripts": {
  "build": "webpack"
}
```
```
$ npm run build
```

あとはejsのscriptタグで読み込みます

```html:./views/index.ejs
<script src="js/bundle.js"></script>
```

サーバーを再起動すると無事にscriptを読み込めました。

```sh

$ node server.js
```
<img width="443" alt="スクリーンショット 2019-10-09 11.44.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171748/453da6ac-64a0-d3cf-784c-08d4292e3511.png">



## その他 快適開発ライフを送るために
できれば設定しておきたいものたち
### ホットリロード
js更新したらbuild, バックエンド更新したらサーバー再起動を毎度行うのは辛すぎる…
とにかく楽にしたいのでホットリロード環境を整えます。

ソースは以下の記事を参考にさせていただきました。
[expressにwebpack-dev-serverを組み込んでフロントエンドとサーバーサイドを同時にサクサク開発するハンズオン ～Auto Reloadで幸せに～](https://qiita.com/riversun/items/e2ab9ac8aa4bde643641)

必要なモジュールを追加でインストールします

```sh
# webpack系
$ npm install --save-dev webpack-dev-server webpack-dev-middleware webpack-hot-middleware

# その他 express, フロント系
$ npm install --save-dev babel-watch @babel/core @babel/preset-env babel-loader core-js@3 @babel/polyfill
```

```js:webpack.config.js
// output.pathに絶対パスを指定する必要があるため、pathモジュールを読み込んでおく
const path = require('path');
// 'production' か 'development' を指定
const MODE = "development";

// ソースマップの利用有無(productionのときはソースマップを利用しない)
const enabledSourceMap = MODE === "development";

// webpack設定
module.exports = {
    mode: MODE,
    devServer: {
        contentBase: path.join(__dirname, '.'),
        port: 8080,
        host: `localhost`,
    },
    // メインとなるJavaScriptファイル（エントリーポイント）
    // babelはES6適用するため
    // hotreloadの設定を配列に追記している
    entry: {
        app:[
            '@babel/polyfill',
            'webpack-hot-middleware/client?reload=true&timeout=1000',
            './src/index.js'
        ]
    },

    // ファイルの出力設定
    output: {
        //  出力ファイルのディレクトリ名
        path: path.join(__dirname, 'dist'),
        // 出力ファイル名
        filename: "bundle.js"
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /(node_modules|bower_components)/,
                use: [{
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            [
                                '@babel/preset-env',
                                {
                                    'modules': 'false', //commonjs,amd,umd,systemjs,auto
                                    'useBuiltIns': 'usage',
                                    'targets': '> 0.25%, not dead',
                                    'corejs': 3
                                }
                            ]
                        ]
                    }
                }]
            },
        ]
    },
    plugins: [
    ],
};
```

expressも追記します。ファイルの上部に以下を追記。

```js:server.js
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const config = require('./webpack.config.js');
const devServerEnabled = true;

if (devServerEnabled) {
    config.plugins.push(new webpack.HotModuleReplacementPlugin());

    const compiler = webpack(config);

    app.use(webpackDevMiddleware(compiler, {
        publicPath: config.output.publicPath
    }));

    app.use(webpackHotMiddleware(compiler));
}
```

あとはpackage.jsonにスクリプトを追記して終了です。
これで`npm start run`するだけでホットリロードする世界が爆誕しました。
幸福度が高い。※ejsの更新は検知出来なかったので追々直したい

```js:package.json
"scripts": {
    "start:client": "webpack-dev-server --config webpack.config.js",
    "start": "babel-watch ./server.js",
}
```
```sh

$ npm run start
```

### Sass
CSSベタ書きは人権が消失するので、できればSassも使えるようにしたいです。
基本的にはnode-sassと各種loaderをインストールしてwebpackにぶちこむだけです。

ただ、そのままstyle-loaderを使うとjs処理が走ってからページにlinkrelを埋め込むので、
一瞬スタイルが当たる前の裸のDOMが出ることがあります。つら。

そのため、CSSはbundle.jsとは別に出力し、先に読ませるように設定していきます。
参考： https://github.com/webpack-contrib/mini-css-extract-plugin

```
npm install -D style-loader css-loader sass-loader node-sass mini-css-extract-plugin
```

```js:webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// module.rules
{
  test: /\.scss/,
  use: [
    {
      loader: MiniCssExtractPlugin.loader 
    },
    {
      loader: 'css-loader',
      options: {
        url: false,
        sourceMap: true,
        importLoaders: 2
      }
    },
    {
      loader: 'sass-loader',
      options: {
        sourceMap: true
      }
    }
  ]
}

plugins: [
  new MiniCssExtractPlugin({
    // viewsで読み込む時のファイル名を指定する
      filename: 'style.css',
  }),
]
```

エントリーポイントで読み込むSassをimportします。
jsと同じように`./src`配下にSassファイルを置けばホットリロードの対象になり、自動コンパイルしてもらえます。最高〜！

```js:src/index.js
import './scss/style.scss'
```

あとはテンプレのheadにスタイルシートを貼ればOK。

```ejs:index.ejs
<link rel="stylesheet" type="text/css" href="style.css">
```

# 最終的なディレクトリ構成
こんな感じになりました。
画像もbundleした方がいいのかなーと思いつつ、今回はあまり必要性を感じなかったのでassetsとして分けちゃいました😔

```console
- node_modules
- assets // 静的ファイル 主に画像
  - images
- src // webpackでbundleするもの
  - js
    hoge.js
  - scss
    _hoge.scss
    style.scss
  - index.js // webpackのエントリーポイント
- views
  hoge.ejs
server.js
.gitignore
package-lock.json
package.json
webpack.config.js
```
あとはExpressとフロントでゴリゴリするだけなので割愛します
# 換装を完走した感想
### サーバーサイドの苦手意識の緩和
ふだんサーバーサイドを触らない＋phpしんどいので滅茶苦茶苦手なんですが、
Node.js+ExpressだとJSのお作法でデータ整形が出来るのでとっつきやすかったです。
jsが使えるの助かる…

### webpackはえらい
今まで雰囲気でwebpackを使っていたんですが、
今回お作法やローダーの役割を考えながら学べたのがよかったです。
Sassの自動コンパイルもささっと作れるのが嬉しい。優しい世界。

### 振り返りは大事
今回は数年前のコードをリファクタしましたが、当時の自分よりは圧倒的成長を感じられたのでよかったです（小並感）
新しい技術をキャッチアップして強みを伸ばすのは大事ですが、たまに振り返ってクソコードをきれいに直すのも勉強になります。
そして数年後、今回作ったものをクソコードと罵る自分がいることを願います。