一篇帶給你 Go 語言的模塊化
前言
在很久很久以前,就 push 自己學過 go 語言,但是之前只是看了一下基礎語法就放棄了,實在是工作當中沒有應用場景。最近發現基于 go 寫的 esbuild 異軍突起,想要深入研究下它的奧秘,發現看不懂。于是,打算先從 go 開始學一遍,等我把 go 學好了,再去研究 esbuild。所以,最近的幾篇文章都會寫 go 的一些學習心得,今天的文章就從 go 語言的模塊化開始。
環境變量
學習 go 語言的第一步,當然是安裝以及環境變量。由于我是 macos,直接運行 brew install go 就能安裝成功,也可以在官網(https://golang.google.cn/)下載對應的二進制包。
安裝成功后,需要配置下面幾個環境變量:
- GOROOT:go 語言的安裝路徑;
- GOBIN:go 語言的可執行文件路徑,一般為 "$GOROOT/bin";
- GOPATH:工作目錄,可設置多個,每個項目都可以設置一個單獨的GOPATH;
GOPATH
在 GoLand(go 語言最強IDE) 中,我們可以在 Preferences 中設置多個 GOPATH,而且將 GOPATH 分為全局和局部的。
GOPATH 最早出現的意義是用來進行模塊管理,每個 GOPATH 中會有三個目錄:
- src:用來存放源代碼;
- pkg:用來存放編譯后的 .a(archive) 靜態庫文件;
- bin:用來存放編譯后可直接運行的二進制文件;
一般設置為工作目錄的 src 文件夾需要手動創建,其他兩個目錄都是編譯后自動生成的。
接下來,我們新建了一個目錄 ~/Code/goland/go-story,并將該目錄設置為工作目錄。
- export GOPATH="~/Code/goland/go-story"
然后在當前目錄新建一個 src 文件夾,并新建一個 hello 目錄,在 hello 目錄新建 main.go 文件。
在 hello/main.go 文件中,寫入如下代碼:
- package main
- import (
- "flag"
- "fmt"
- )
- var name string
- func init() {
- flag.StringVar(&name, "name", "everyone", "The greeting object.")
- }
- func main() {
- flag.Parse() // 解析命令行參數
- fmt.Printf("\nHello %s\n", name)
- }
flag 庫是 go 內置的模塊,類似于 node 的 commander 庫,運行后結果如下所示:
下面我們引入一個能夠讓命令行輸出色彩更加豐富的庫:colourize,類似于 node 中的 chalk。通過下面這個命令來安裝依賴:
- go get github.com/TreyBastian/colourize
運行之后,我們可以看到在工作區自動創建了一個 pkg 目錄,目錄下新生成的是 colourize 庫文件,同時 src 目錄也新建了一個 github.com 目錄,用來放 colourize 的源碼。
go get 命令可以簡單理解為 npm install。接下來就能在 hello/main.go 中引入依賴。
- package main
- import (
- "flag"
- "fmt"
- "github.com/TreyBastian/colourize"
- )
- var name string
- func init() {
- flag.StringVar(&name, "name", "everyone", "The greeting object.")
- }
- func hello(name string) {
- fmt.Printf(colourize.Colourize("\nHello %s\n", colourize.Blue), name)
- }
- func main() {
- flag.Parse()
- hello(name)
- }
運行 hello/main.go 可以看到命令行輸出了藍色的文字。
默認情況下,go 依賴的加載機制為:
- $GOROOT 下的 src 目錄
- $GOPATH 下的 src 目錄
Go Vendor
前面這種方式,有個很麻煩的問題,就是沒有辦法進行很好的版本管理,而且多個依賴分散在 $GOPATH/src 目錄下,可能會出現很多很麻煩的問題。
例如,我現在在 GOPATH 下有兩個項目:go-blog、go-stroy,這兩個項目分別有不同的依賴,分散在 github.com 目錄,這個時候到底要不要將整個 github.com 目錄添加到版本庫呢?
go 在 1.5 版本的時候,引入了 vendor 機制,在每個項目目錄下可以通過 vendor 目錄存放依賴,這類似于 node 中的 node_modules 目錄。
使用 go vendor 需要先安裝 govendor 模塊。
- go get govendor
然后在項目目錄運行如下命令。
- cd ~/Code/gland/go-story/src/hello
- govendor init
- govendor add github.com/TreyBastian/colourize
可以看到,hello 項目下新生成了一個 vendor 目錄,而且 colourize 也被拷貝到了該目錄下。
而且 govendor 會新建一個 vendor.json 文件,用來進行依賴項的管理。
有了 go vendor 之后,依賴項的加載順序如下:
- 項目目錄下的 vendor 目錄
- 項目目錄上一級的 vendor 目錄
- 不斷向上冒泡 ……(PS. 類似于 node_modules)
- $GOPATH 下的 vendor 目錄
- $GOROOT 下的 src 目錄
- $GOPATH 下的 src 目錄
配置開關
有一點需要注意,在 go 1.5 版本下,go vendor 并不是默認開啟的,需要手動配置環境變量:
- export GO15VENDOREXPERIMENT=1
在 go 1.6 版本中,go vendor 已經改為默認開啟。
Go Modules
雖然 1.5 版本推出了 go vendor,但是沒有解決根本問題,只是依賴的查找上支持到了 vendor 目錄,vendor 目錄還是需要一些第三方的庫(govendor、godep、glide)進行管理,而且對于 GOPATH 環境變量依然有所依賴。
官方為了解決這些問題,終于在 1.11 版本中,實驗性的內置了其模塊管理的能力(1.12 版本正式開啟):go mod。
使用 go mod 的時候,我們無需 GOPATH,所以我們需要把之前配置的 GOPATH 清理掉,調整下目錄結構,將 go-story/hello/main.go 直接移動到 go-story/main.go,然后將 src、pkg 目錄刪除。
- # 初始化 go modules
- go mod init [pkg-name]
此時,會在目錄下生成一個 go.mod 文件。
查看其內容,發現里面會聲明 go 的版本號,以及當前模塊的名稱。
然后我們安裝依賴(不管是何種依賴管理的方式,安裝方法依舊不變):
- go get github.com/TreyBastian/colourize
go.mod 中,會寫入添加的依賴,以及版本號,同時,該模塊會被安裝到 GOPATH 中。由于我們之前將 GOPATH 移除,這里會安裝到 GOPATH 的默認值中(~/go/)。
總結
之前開發 node 的過程中,也踩過很多 npm 的坑,而且社區對 npm 也有很多怨言,也出現了很多第三方的模塊:yarn、pnpm 等等。
想不到 go 的模塊管理,也是一部血淚史,現在下載一些 go 的老項目還會發現一些 go vendor 管理方式的項目。另外,go mod 出現后,go 官方也在計劃移除 GOPATH。