AngularのプロジェクトをビルドしてGolangで動かす
はじめに
以前作成したAngularのプロジェクトをビルドして、 Golangを使って表示してみます。
Angularのプロジェクトをビルドする
実は最近のJS系の勉強会などに参加していて気になっていたことがあります。
「本番ではどうやって表示するの?」
例えばバックエンドをSpring Bootで構築したとして、
フレームワークを使わずに作った場合はSpring Bootのプロジェクトに組み込んで、
Thymeleafなどを使って表示するかと思います。
しかし、例えばAngularの場合だとAngular-cliを使って、
Angularのプロジェクト単体で表示します(確認の段階では)。
では、それぞれを完全に別に扱うの…?
などなど考えていたのですが、実際にはAngularのプロジェクトをビルドすることで、
上の例であればSpring Bootのプロジェクトに組み込むことができる、ということでした。
ビルド自体は(とりあえずビルドするだけなら)簡単で、Angularのプロジェクト直下で下記を実行するだけです。
ng build
ビルドに成功すればプロジェクト直下に「dist」というフォルダが作成され、
必要なファイルがその中に出力されます。
あとはバックエンド側のプロジェクトにまとめて放り込んで、
「index.html」を表示してあげればOKです。
今回はGolangのファイル(webtest.go)と同じディレクトリに「templates」というディレクトリを作り、
その中に全てファイルを保存しました。
HTMLファイルを読み込む
何故か我が家にある(買った)Go言語によるWebアプリケーション開発で扱われているコードを参考にHTMLを表示してみます。
https://github.com/matryer/goblueprints/blob/master/chapter1/chat/main.go
webtest.go
package main import ( "html/template" "log" "net/http" "path/filepath" "sync" ) type templateHandler struct { once sync.Once filename string viewTemplate *template.Template } func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { t.once.Do(func() { t.viewTemplate = template.Must(template.ParseFiles(filepath.Join("templates", t.filename))) }) t.viewTemplate.Execute(w, nil) } func main() { http.Handle("/", &templateHandler{filename: "index.html"}) // Portを指定してWebサーバーを開始. if err := http.ListenAndServe(":8088", nil); err != nil { // エラーが発生すればログ出力. log.Fatal("ListenAndServe:", err) } }
http.Handle, ServeHTTP
ファイル読み込みのキーになる部分がhttp.Handle、ServeHTTPです。
http.Handle
第一引数に対象とするURL(上記ではルートディレクトリ)を、
第二引数にhttp.Handlerという関数ServeHTTPを持つインターフェースを取ります。
で、対象URLにアクセスした時、Spring bootでいう「@RequestMapping」のように、
第二引数から関数ServeHTTPが実行され、「index.html」が表示される、という流れになっています。
ServeHTTP
ここで気になるところとして、上記では第二引数としてhttp.Handlerではなく、構造体を渡しています。
これがエラーにならないのは、
Golangでは構造体があるインターフェース(ここではhttp.Handler)が持つ関数を全て持っている場合、
そのインターフェースと同じものとして扱うことができるためです。
なお構造体に関数を持たせているのがServeHTTPの「(t *templateHandler)」の部分です。
便利ですが、なかなか慣れないとナンノコッチャ分からんですね。。。(;・∀・)
その他の詳しい解説は本を参照。
…なのですが、この状態ではLoading…の文字しか表示されません。
index.htmlから呼び出しているJavascriptにアクセスできないためです。
また同じ理由でFaviconも表示できません。
静的ファイルを読み込む
これらのファイルにアクセスできるようにするためにはhttp.Handleを使います。
webtest.go
// Javascriptのファイルを読み込む. http.Handle("/js/", http.StripPrefix("/js/", http.FileServer(http.Dir("templates")))) // 画像ファイルを読み込む. http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("templates"))))
そして、ng buildで出力したindex.htmlを少し変更します。
MainPage.html
< !doctype html> < html> < head> < meta charset="utf-8"> < title>NgCustomer< /title> < base href="/"> < meta name="viewport" content="width=device-width, initial-scale=1"> < link rel="icon" type="image/x-icon" href="img/favicon.ico"> < /head> < body> < app-root>Loading...< /app-root> < script type="text/javascript" src="js/inline.bundle.js">< /script> < script type="text/javascript" src="js/polyfills.bundle.js">< /script> < script type="text/javascript" src="js/styles.bundle.js">< /script> < script type="text/javascript" src="js/vendor.bundle.js">< /script> < script type="text/javascript" src="js/main.bundle.js">< /script> < /body> < /html>
変更の理由は、http.Handleの第一引数で指定するURLが、index.htmlと重複するのを避けるためです。
第二引数「http.StripPrefix(“/js/”, http.FileServer(http.Dir(“templates”)))」は、
(例えば)アクセスされるURL「/js/inline.bundle.js」から「/js/」の部分を取り除き、
templatesディレクトリ内にあるinline.bundle.jsを取得します。
とりあえず、これでAngularのファイルを表示できるようになりました。
まだ怪しい要素もありますが、抜けなどがあれば追加・修正します。
他からアクセスできるようにする
確認の際は「go run webtest.go」を実行していますが、
例の如く?他のマシンからこのページにアクセスすることはできません。
どうするかというと、これも例によってオプションを追加します。
go run webtest.go -serve 0.0.0.0