レガシーな構成のWebアプリをWebpackで結合＆難読化

# TL;DR

HTML上でJavaScriptの読込の順序が正しくないと動かないような構成が使われたレガシーなWebアプリをElectronで固める際、WebPackで結合(minimize)した上で難読化をかけるような形に修正する場合のサンプルです。

+ webpack-cli@3.3.10
+ webpack@4.41.4
+ webpack-obfuscator@0.18.8

## 元の構成

レガシーなWebアプリの例として、最初に読み込むスクリプトで宣言したファンクションを後続のJavaScriptで読み込んでいる(順番が正しくないとundefinedエラーになる)ようなケースを想定しています。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head></head>
<body>
  <script src="js/called.js"></script> <!-- 最初に読み込むスクリプト -->
  <script src="js/index.js"></script> <!-- 2番目に読み込むスクリプト -->
</body>
</html>
```

呼び出される側では `testFunction` を宣言して

```javascript:called.js
console.log("called.js")
const hoge = "Hellow World"

function testFunction(){
  console.log("its test function!")
}
```

後続のJavaScriptでは `testFunction` を呼び出しているパターン。

```javascript:index.js
console.log("index.js")
console.log("hoge:" + hoge)
testFunction()
```

## Webpackによる結合

複数に分かれたJavaScriptファイルを統合するために、WebPackを利用します。

WebPack用のコンフィグファイルでは、エントリーポイント(結合の起点となるソース)を指定し、結合した結果を `bundle.js` として出力します。

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

module.exports = {
  mode: "production",
  entry: './index.js',
  output: {
    path: path.resolve(__dirname),
    filename: 'bundle.js'
  },
  plugins: [],
};
```

HTML側では結合した `bundle.js` のみ読み込むように修正。

```html:index.html&nbsp;修正後
<!DOCTYPE html>
<html lang="ja">
<head></head>
<body>
  <script src="js/bundle.js"></script>
</body>
</html>
```

依存関係を表現するために`require()`で対象のスクリプトを順番に読み込ませる。
今回は `index.js` で `called.js` をrequireするようにしておきます。

```javascript:index.js&nbsp;修正後
require('./called.js')
console.log("index.js")
console.log("hoge:" + hoge)
testFunction()
```

読みだされる(先行して実行される)側のスクリプトでは、Webpackでの結合時に向けて、既存のファンクションをglobalに対して宣言するようにしておきます。

```javascript:called.js&nbsp;修正後
console.log("called.js")
global.hoge = "Hellow World"

global.testFunction = function (){
  console.log("its test function!")
}
```

出てきた結果がこちら。

```javascript:bundle.js
!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){n(1),console.log("index.js"),console.log("hoge:"+hoge),testFunction()},function(e,t,n){(function(e){console.log("called.js"),e.hoge="Hellow World",e.testFunction=function(){console.log("its test function!")}}).call(this,n(2))},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n}]);
```

## Obfuscator

1ファイルへの結合が終わったので、その状態からさらに難読化を実施するように設定します。
難読化は`Obfuscator` プラグインを用いてWebpackの際に併せて実行するように修正。

```javascript:webpack.config.js&nbsp;修正後
const path = require('path')
const webpack = require('webpack')
const Obfuscator = require('webpack-obfuscator')

module.exports = {
  mode: "production",
  entry: './index.js',
  output: {
    path: path.resolve(__dirname),
    filename: 'bundle.js'
  },
  plugins: [
    new Obfuscator({rotateUnicodeArray: true}, [])
  ],
};
```

難読化した結果は以下の通り。設定にもよりますが、パッと見では何やってるかわからない程度にはなったかと思います。

```javascript:bundle.js
var _0x1fd2=['default','prototype','hasOwnProperty','hoge:','log','Hellow\x20World','return\x20this','exports','call','defineProperty','undefined','toStringTag','Module','object','__esModule','create','string','bind'];(function(_0x514b58,_0x2d74a7){var _0x4d6190=function(_0x1b37bd){while(--_0x1b37bd){_0x514b58['push'](_0x514b58['shift']());}};_0x4d6190(++_0x2d74a7);}(_0x1fd2,0x73));var _0x1932=function(_0x1a026c,_0x2492de){_0x1a026c=_0x1a026c-0x0;var _0x2d8f05=_0x1fd2[_0x1a026c];return _0x2d8f05;};!function(_0x5bd143){var _0x547e8c={};function _0x3e9ecd(_0x15cd24){if(_0x547e8c[_0x15cd24])return _0x547e8c[_0x15cd24][_0x1932('0x0')];var _0x1dd3db=_0x547e8c[_0x15cd24]={'i':_0x15cd24,'l':!0x1,'exports':{}};return _0x5bd143[_0x15cd24][_0x1932('0x1')](_0x1dd3db['exports'],_0x1dd3db,_0x1dd3db['exports'],_0x3e9ecd),_0x1dd3db['l']=!0x0,_0x1dd3db[_0x1932('0x0')];}_0x3e9ecd['m']=_0x5bd143,_0x3e9ecd['c']=_0x547e8c,_0x3e9ecd['d']=function(_0x5bd143,_0x547e8c,_0x882386){_0x3e9ecd['o'](_0x5bd143,_0x547e8c)||Object[_0x1932('0x2')](_0x5bd143,_0x547e8c,{'enumerable':!0x0,'get':_0x882386});},_0x3e9ecd['r']=function(_0x5bd143){_0x1932('0x3')!=typeof Symbol&&Symbol[_0x1932('0x4')]&&Object[_0x1932('0x2')](_0x5bd143,Symbol[_0x1932('0x4')],{'value':_0x1932('0x5')}),Object['defineProperty'](_0x5bd143,'__esModule',{'value':!0x0});},_0x3e9ecd['t']=function(_0x5bd143,_0x547e8c){if(0x1&_0x547e8c&&(_0x5bd143=_0x3e9ecd(_0x5bd143)),0x8&_0x547e8c)return _0x5bd143;if(0x4&_0x547e8c&&_0x1932('0x6')==typeof _0x5bd143&&_0x5bd143&&_0x5bd143[_0x1932('0x7')])return _0x5bd143;var _0x1b5cad=Object[_0x1932('0x8')](null);if(_0x3e9ecd['r'](_0x1b5cad),Object['defineProperty'](_0x1b5cad,'default',{'enumerable':!0x0,'value':_0x5bd143}),0x2&_0x547e8c&&_0x1932('0x9')!=typeof _0x5bd143)for(var _0x435945 in _0x5bd143)_0x3e9ecd['d'](_0x1b5cad,_0x435945,function(_0x547e8c){return _0x5bd143[_0x547e8c];}[_0x1932('0xa')](null,_0x435945));return _0x1b5cad;},_0x3e9ecd['n']=function(_0x5bd143){var _0x547e8c=_0x5bd143&&_0x5bd143['__esModule']?function(){return _0x5bd143[_0x1932('0xb')];}:function(){return _0x5bd143;};return _0x3e9ecd['d'](_0x547e8c,'a',_0x547e8c),_0x547e8c;},_0x3e9ecd['o']=function(_0x5bd143,_0x547e8c){return Object[_0x1932('0xc')][_0x1932('0xd')]['call'](_0x5bd143,_0x547e8c);},_0x3e9ecd['p']='',_0x3e9ecd(_0x3e9ecd['s']=0x0);}([function(_0x252c19,_0x267b5d,_0x257f03){_0x257f03(0x1),console['log']('index.js'),console['log'](_0x1932('0xe')+hoge),testFunction();},function(_0x172b2a,_0x83088,_0x15c137){(function(_0x172b2a){console[_0x1932('0xf')]('called.js'),_0x172b2a['hoge']=_0x1932('0x10'),_0x172b2a['testFunction']=function(){console[_0x1932('0xf')]('its\x20test\x20function!');};}[_0x1932('0x1')](this,_0x15c137(0x2)));},function(_0x4ee20f,_0x53d713){var _0x3860ae;_0x3860ae=function(){return this;}();try{_0x3860ae=_0x3860ae||new Function(_0x1932('0x11'))();}catch(_0x4c52d0){_0x1932('0x6')==typeof window&&(_0x3860ae=window);}_0x4ee20f[_0x1932('0x0')]=_0x3860ae;}]);
```

本来は正しくモジュール化していくのが本筋ですが、あまりにもスパゲティ化してしまったコードをあまりいじらずにまとめるならこんなやり方もありますよ、ということで。