豆腐とコンソメ

豆腐とコンソメ

もろもろのプログラム勉強記録

受託案件のWeb系の納品物ってビルド後のソースとソース一式、どっちを渡してる?

前置き

Web案件の受託開発をしていてふと思った疑問。

納品するときって、ソースコード一式 or Webpackでビルド後のソースのどっちを納品してますか?

大抵はソースコード一式を納品しているような気もするんだけれども、わりとビルド後のソースだけを納品してOKっていうパターンもあったりする。

これで問題になったことはそんない。
でも、たまに納品後にお客さん側でCSSやHTML、もしくはjavascriptをちょっと修正する、みたいなことがあったりする。

その際に、リソースをminifyしてたりすると、なんぞこれ!と言われてしまうわけです。

コンパイル型言語であれば、納品先でちょっと修正するなんていう発想はそもそもないんだろうけれども、javascriptのようなスクリプト言語だったり、HTML・CSSだったら、お客さん側でも、俺わかるから!って人が修正するみたいなケースがあるんじゃないかと思う。

そもそも、そっちで修正するケースなんて想定してねえよ!といったり、ではソースコード一式渡すんで、Node.jsいれてビルドしてね!!!って言うだけの話っちゃ話なんですが、なんでビルドなんかしてるのか説明しろ!といわれた場合に備え、ちゃんと答えられるようにしときたいと思った今日このごろ。


本題

ということで、やっぱりWebpackとかの話になる。
以前も↓の記事でぜんぜんわかねえという感想だけを書いた記事に引き続き再整理。
ちょっとわかってきた気がする。

www.tohuandkonsome.site

検証環境とメモ

  • Chromeで実行
  • webpack-dev-serverとhttp-serverで確認


そもそもなんでビルドしてんの?

いろいろ考えたんですが、開発効率をあげるために、ビルドしてますってことだと思う。


開発効率をあげるって?

なんでビルドすると開発効率があがんの?って話です。

突き詰めると、このへんでしょうか。

  • 機能ごとにファイルを分割して管理したい。そのほうが開発作業がスムーズ。
  • ES6構文やTypeScriptを使って効率よくコードを書きたい。
  • 使用したいフレームワーク・ライブラリがビルド前提。

以降では、1点目のファイル分割に注力して記載することにします。
※他も整理しようかとおもったけれども、心が折れた。


機能ごとにファイルを分割するって?

さて、ここに簡単なHTML + javascriptを用意してみました。

以下の通り、ユーザー名を投稿するだけのシンプルなフォームです。
唯一の特徴があるとすると、ボタンを押したときに、ユーザー名が未入力だとエラーメッセージをクライアント側で出力しているぐらいです。

f:id:konoemario:20181122174429p:plainf:id:konoemario:20181122174452p:plain
かんたんなフォーム


実際のコードは以下の通りになります。

index.html

  <form id="form" action="/hoge">
    <div class='input'>
      <label for="user-name">ユーザー名</label>
      <input id="user-name" type="text">
    </div>
    <div class='error'>
      <label id="user-name-error" style="color: red"></label>
    </div>
    <button id="submit">Submit</button>
  </form>
  <script>
    //formのsubmitボタンを押したときのイベント
    document.getElementById('form').addEventListener('submit', function(event) {
      //ユーザー名をとってきて
      var userName = document.getElementById('user-name').value

      //ユーザー名がからっぽなら、エラーメッセージを出す
      if(userName === '') {
        document.getElementById('user-name-error').innerHTML = 'ユーザーネームを入力してください'
        event.preventDefault()
      }
    })
  </script>


現在、ひとつのファイルにHTMLとjavascriptとが一緒になっています。
これぐらいの規模であれば、HTMLとjavascriptが一緒になってても特段問題ないのですが、

  • ユーザー名だけではなく、生年月日やEメール等、入力項目が増えるたびにどんどんコードが増えていく
  • 複数人で編集するときに`index.html'のコンフリクトが半端ない

と問題が増えていくことが考えられます。

そもそもファイル内のコードが長いと、コード間を移動するのもめんどくさいし、差分も取りづらい、とデメリットがいっぱいありますよね。

なので、ファイルをHTMLとjavascriptをちゃんとわけることにします。

index.html

  <form id="form" action="/hoge">
    <div class='input'>
      <label for="user-name">ユーザー名</label>
      <input id="user-name" type="text">
    </div>
    <div class='error'>
      <label id="user-name-error" style="color: red"></label>
    </div>
    <button id="submit">Submit</button>
  </form>
  <script src="main.js"></script>


main.js

//formのsubmitボタンを押したときのイベント
document.getElementById('form').addEventListener('submit', function(event) {
  //ユーザー名をとってきて
  var userName = document.getElementById('user-name').value

  //ユーザー名がからっぽなら、エラーメッセージを出す
  if(userName === '') {
    document.getElementById('user-name-error').innerHTML = 'ユーザーネームを入力してください'
    event.preventDefault()
  }
})


単純に、javascriptの内容をmain.jsに切り出して、index.html側から参照しているだけですね。
ちょっとしたjavascriptを書く場合は、こんなふうにしていることが一般的かと思います。

※とはいえ、Googleアナリティクスとかのガジェット系の埋め込みはHTMLに直接書いちゃいますが。


さらに分割する

さて、さきほどhtmlとjavascriptを分割しました。

ですが、main.jsもチェック内容やらなんやらが増えていくと、コードが肥大化していきます。

例えば、Eメールアドレスの項目が増えたので、こちらのバリデーションチェックも追加することにしました。

Eメールアドレスが増えた

//formのsubmitボタンを押したときのイベント
document.getElementById('form').addEventListener('submit', function(event) {
  //
  var userName = document.getElementById('user-name').value
  var email = document.getElementById('email').value

  //ユーザー名がからっぽなら、エラーメッセージを出す
  if(userName === '') {
    document.getElementById('user-name-error').innerHTML = 'ユーザーネームを入力してください'
  }

  //Eメールが空っぽ
  if(email === '') {
    document.getElementById('email-error').innerHTML = 'Eメールアドレスを入力してください'
  }

  //Eメールの形式が不正
  if(!email.match( /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/)) {
    document.getElementById('email-error').innerHTML = 'Eメールアドレスの形式がおかしいです'
  }
})


そもそもの設計が~とかはあるんですが、この状態では項目が増えていく度にコードが増えていくことが考えられます。

なので、ちょっと構成を見直してみます。 あんまりいい例ではないのですが、項目が正しいかどうかをチェックする機能を汎用的に考え、別の関数として切り出します。

ここでは、空っぽかどうかを判定するisEmpty関数と、Eメールアドレスが正しいかを確認する、isEmail関数をつくることにしました。

チェック内容を関数として切り出す

//formのsubmitボタンを押したときのイベント
document.getElementById('form').addEventListener('submit', function(event) {
  //
  var userName = document.getElementById('user-name').value
  var email = document.getElementById('email').value

  if(isEmpty(userName)) {
    document.getElementById('user-name-error').innerHTML = 'ユーザーネームを入力してください'
    event.preventDefault()
  }

  if(isEmpty(email)) {
    document.getElementById('email-error').innerHTML = 'Eメールアドレスを入力してください'
    event.preventDefault()
  }

  if(!isEmail(email)) {
    document.getElementById('email-error').innerHTML = 'Eメールアドレスの形式がおかしいです'
    event.preventDefault()
  }

})

// Eメールアドレスの形式チェック
function isEmail(value) {
  if(value.match( /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/)) {
    return true
  }
  return false
}

// 空っぽかどうか
function isEmpty(value) {
  return value === ''
}

さて関数化することで、別の箇所でEメールの項目がでてきたとしても、この関数を使うことでチェックができるようになりましたね。
ただ、関数をmain.jsに書いちゃうと、コードが肥大化していくという問題は解消されません。

であれば、関数は別のファイルvalidate.jsに切り出すことにします。

validate.js

// Eメールアドレスの形式チェック
function isEmail(value) {
  if(value.match( /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/)) {
    return true
  }
  return false
}

// 空っぽかどうか
function isEmpty(value) {
  return value === ''
}


ファイルを分割したけれども

切り出したはいいのですが、このままだとmain.js内からisEmpty関数やisEmail関数を参照することはできません。
なので、index.htmlにて、`validate.js'も読み込んでおくようにしておきます。

index.html

  <script src="validate.js"></script>
  <script src="main.js"></script>
</body>
</html>

これでも、大丈夫といえば大丈夫なんですが、気になる点がいくつかあります。

  • main.jsvalidate.jsの読み込み順を意識する必要がある。
    main.js内がisEmpty等の関数を呼び出すタイミングで、validate.jsの読み込みが終わっていないと、そんな関数ねえよと怒られてしまいます。
    今回は、画面のボタンを押さない限り、関数が呼び出されないので、読み込み順を逆にしても大きな問題はないのですが、あまりいい状態とはいえません。

  • サーバーに対してのリクエスト数が増える 性能とかを気にしない環境であれば、そこまで問題はないのですが、validate.jsmain.jsとでわかれている場合、サーバに対するリクエストは以下のように2回リクエストが発生します。

f:id:konoemario:20181127152901p:plain
一言でおわる話を図式化した

だから何よ?ってなるかもしれませんが、このリクエスト数っていうのが性能を考えるにあたって、結構大きな影響を与えます。

なので、できれば1回のリクエストでまとめてとってこれたほうがサーバーにとってはうれしかったりします。

※といいつつ、あんまりわかってない。Apacheのpreforkタイプだったら、リクエストがある度にプロセスをforkしちゃうからやべえんだよってことなのかな?Nginxとかだったらそこまででもなかったり?とはいえファイルオープンとかのシステムコールの発生回数は増えるんだからってことなのかしら。TCPのコネクションは使いまわすからだいじょうぶ?

この他にも変数のスコープの話とかもありそうなんですが、ちょっと理解が足りてないんで割愛します。


importとexportを使う

さて、ちょっと話がそれます。

さきほど、ファイルを分割したあとに、main.jsからvalidate.jsを参照できるように読み込み順を意識してindex.htmlに記載しました。

index.html

  <script src="validate.js"></script>
  <script src="main.js"></script>
</body>
</html>

しかし、こんなことをしなくてもjavascriptにはimport/exportという構文があります。
正確には、javascriptのES6構文から使えるようなりました。

さきほどのvalidate.jsの関数にexportを付与してあげます。

validate.js

// Eメールアドレスの形式チェック
export function isEmail(value) {
  if(value.match( /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/)) {
    return true
  }
  return false
}

// 空っぽかどうか
export function isEmpty(value) {
  return value === ''
}

そして、main.jsにはvalidate.jsの関数を参照するというimport分を追加してあげます。

main.js

import { isEmpty, isEmail } from './validate.js'

//formのsubmitボタンを押したときのイベント
document.getElementById('form').addEventListener('submit', function(event) {
  //
  var userName = document.getElementById('user-name').value
  var email = document.getElementById('email').value

  if(isEmpty(userName)) {
    document.getElementById('user-name-error').innerHTML = 'ユーザーネームを入力してください'
    event.preventDefault()
  }

  if(isEmpty(email)) {
    document.getElementById('email-error').innerHTML = 'Eメールアドレスを入力してください'
    event.preventDefault()
  }

  if(!isEmail(email)) {
    document.getElementById('email-error').innerHTML = 'Eメールアドレスの形式がおかしいです'
    event.preventDefault()
  }

})

この上で、index.htmlのscriptタグのtypeをmoduleに変更すると、validate.jsindex.htmlに書かなくとも、関数を参照することができるようになります!

index.html

  <script type="module" src="main.js"></script>

この方法で、先ほどの

  • 読み込み順を意識しなきゃいけない
  • リクエスト数が増える

の問題が解決されたかのように思えますが、後者のリクエスト数が増えるという問題は解決されていません。
というのも、結局のところmain.jsvalidate.jsをそれぞれとってきた後に、import/exportでうまいことブラウザがやってくれているからです。

しかし、ES6構文の話にもなりますが、このimport/exportはIE11だと動かないです。


結局どうすんのよ

開発時にファイルは分割して管理したいけれども、ブラウザが使うときは一個にまとめちゃいなよ!という発想になります。

f:id:konoemario:20181127155541p:plain

さて、これを実現することができるのがWebpackになります。

Webpackを通すことで、分割されたファイルをひとつのファイル(ここではindex.js')にまとめることができます。 まとめることができた半面、作成されたindex.js`は一見するとなにがなんだかわからないコードになってしまいます。

開発者はうれしい!
サーバーの性能という観点からもうれしい!

ただ、index.jsを直接修正するのはやめたほうがいいですね。