【メモ】Google Closure Compilerを使う


Google Closure Compilerとは?

https://developers.google.com/closure/compiler/
https://github.com/google/closure-compiler

JavaScriptをJavaScriptに変換するコンパイラー。コードをより効率的な形に変換してくれる。ClojureScriptが使っていたりする。

その昔は独自のモジュールシステムを持っていたのだが、いつのまにかnpm経由で使えるようになっていたり、nodeモジュールに対応していたり、いろいろ進歩している模様。

package.json

{
    "name": "teach-yourself-closure-compile",
    "version": "1.0.0",
    "scripts": {
        "build": "..."
   },
    "dependencies": {
        "immutable": "^4.0.0-rc.12",
        "luxon": "^1.8.2",
        "react": "^16.6.3",
        "react-dom": "^16.6.3"
    },
    "devDependencies": {
        "@babel/cli": "^7.2.0",
        "@babel/core": "^7.2.0",
        "@babel/preset-react": "^7.0.0",
        "babel-loader": "^8.0.4",
        "cpx": "^1.5.0",
        "google-closure-compiler": "^20181210.0.0",
        "rimraf": "^2.6.2"
    }
}

今回はReactでごく簡単なサンプルを作り、動作させることを目標とする。ついでなのでいくつかライブラリーを入れてみる。
ビルド手順は後述。

index.jsx

index.jsx
const React = require("react")
const ReactDOM = require("react-dom")
const ImMap = require("immutable/dist/immutable").Map
const DateTime = require("luxon/build/node/luxon").DateTime

class Application extends React.Component {

    constructor(props) {
        super(props);
        this.state = { date: DateTime.local().toISO() }
    }

    componentDidMount() {
        this.timer = setInterval(() => this.setState({ date: DateTime.local().toISO() }), 500)
    }

    componentWillUnmount() {
        clearTimeout(this.timer)
    }

    render() {
        return (
            <div>
                <h1>hello, closure compiler!</h1>
                <p>User: {this.props.user.get("firstName")} {this.props.user.get("lastName")}</p>
                <p>Current date: {this.state.date}</p>
            </div>
        )
    }
}

ReactDOM.render(
    <Application user={ImMap({ firstName: "John", lastName: "Smith" })} />,
    document.getElementById("app")
)

どうもデフォルトインポートをうまく扱えないようで断念。requireでロードする。
またpackage.jsonの指定ファイルを見てくれないのか、require("immutable")ではだめで、require("immutable/dist/immutable")とファイルのパスを指定する必要があった。

immutable.jsのMapImMapにリネームしているのは、バインディングが重複しているというエラーが出たため。

ビルド

Babel

まずJSXを普通のJSに変換する。ここではBabelを使用。

.babelrc
{
    "presets": [
        "@babel/preset-react"
    ],
    "compact": false,
    "comments": true
}
babel src --out-dir ./out/babel

コンパイル

google-closure-compiler --js out/babel/index.js --js node_modules/react --js node_modules/react-dom/index.js --js node_modules/react-dom/cjs/react-dom.production.min.js --js node_modules/react-dom/cjs/react-dom.development.js  --js node_modules/object-assign --js node_modules/prop-types --js node_modules/scheduler --js node_modules/luxon/build/node/luxon.js --js node_modules/immutable/dist/immutable.js --entry_point out/babel/index.js --js_output_file out/closure/index.js --module_resolution NODE --dependency_mode LOOSE --process_common_js_modules --language_in ECMASCRIPT_NEXT

--jsフラグで入力ファイルを指定。見てのとおり、モジュールもすべて指定する必要がある。そんなわけないだろうと思うのだが、それらしいオプションを発見できず断念。知っている方教えてください。

--entry_pointでコンパイルを始めるエントリーポイントを指定する。今回はBabelでコンパイルされたファイルを指定。

--js_output_fileは出力先。

--module_resolution NODE --process_common_js_modulesでnodeモジュールを使っていることを伝える。

--language_in ECMASCRIPT_NEXTでECMAスクリプトバージョンを指定する。

フラグ一覧はこちら

その他

  • React DevToolsが動かなかった。これは悲しい。
  • ライブラリーもコンパイルされるので、警告が大量に出力される。--warnings_whitelist_fileフラグでホワイトリストファイルを作ればよさそうだが、さすがに面倒くさすぎる。
  • 今回のソースのリポジトリはこちら