6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-10-10

限界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で構築しました。

初期設定


$ 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を使ってみます。


$ npm install --save-dev express ejs

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

./views/index.ejs

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

expressの設定

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

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});
})

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


$ node server.js

https://localhost.8080/
アクセスするとHelloWorldできています。やったぜ。
スクリーンショット 2019-10-09 11.34.58.png

webpackでbundleする

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

webpackの設定

この辺は公式ドキュメントとほぼ同じです。

$ npm install webpack webpack-cli --save-dev
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。

./src/js/sushi.js
export default () => {
  alert('🍣')
}
./src/index.js
// import文を使って sushi.js ファイルを読み込む。
import sushi from './js/sushi.js'

// sushi.jsに定義されたJavaScriptを実行する。
sushi()

ただ、webpackコマンドを叩かないといつまでたってもbundleされないので
package.jsonでbuildスクリプトを追加しておきましょう。

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

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

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

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


$ node server.js
スクリーンショット 2019-10-09 11.44.16.png

その他 快適開発ライフを送るために

できれば設定しておきたいものたち

ホットリロード

js更新したらbuild, バックエンド更新したらサーバー再起動を毎度行うのは辛すぎる…
とにかく楽にしたいのでホットリロード環境を整えます。

ソースは以下の記事を参考にさせていただきました。
expressにwebpack-dev-serverを組み込んでフロントエンドとサーバーサイドを同時にサクサク開発するハンズオン ~Auto Reloadで幸せに~

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

# 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
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も追記します。ファイルの上部に以下を追記。

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の更新は検知出来なかったので追々直したい

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

$ 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
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ファイルを置けばホットリロードの対象になり、自動コンパイルしてもらえます。最高〜!

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

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

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

最終的なディレクトリ構成

こんな感じになりました。
画像もbundleした方がいいのかなーと思いつつ、今回はあまり必要性を感じなかったのでassetsとして分けちゃいました😔

- 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の自動コンパイルもささっと作れるのが嬉しい。優しい世界。

振り返りは大事

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

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?