ข้ามไปยังเนื้อหา

Modules เชิงลึก

ถ้าคุณรู้จัก npm ก็เท่ากับเข้าใจ Go modules ไปแล้ว 90% ทั้งสองระบบใช้ semantic versioning, lock file และ manifest file เหมือนกัน mental model จึง map กันได้ลงตัว — แต่กลไกเบื้องหลังต่างกันในจุดสำคัญ

TypeScript
// package.json
{
"name": "my-app",
"version": "1.0.0",
"dependencies": {
"express": "^4.18.2",
"lodash": "^4.17.21"
},
"devDependencies": {
"jest": "^29.0.0"
}
}
Go
// go.mod
module github.com/myorg/my-app
go 1.27
require (
github.com/gin-gonic/gin v1.9.1
github.com/stretchr/testify v1.8.4
)
// No separate devDependencies in Go.
// Test-only deps are included in require; the
// compiler excludes them from production binaries.

go.mod เทียบเท่า package.json มี field ที่ต้องมี 3 ตัว:

module github.com/myorg/my-app ← module path (import prefix)
go 1.27 ← minimum Go version
require (...) ← direct dependencies

จุดที่ต่างจาก package.json:

  • module path เป็น string คล้าย URL มักตรงกับ path ของ repo บน VCS
  • ไม่มี devDependencies dependency ทุกตัวอยู่ใน require แต่ Go linker จะรวมเฉพาะ package ที่ binary นั้น ๆ import จริง ๆ
  • ไม่มี syntax range แบบ ^ คุณ pin version แบบเป๊ะ ๆ (เช่น v1.9.1)

go.sum เทียบเท่า package-lock.json เก็บ cryptographic hash ของทุก dependency แต่ละ version:

github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BerypkHKpy7LtzLuviaCYD2h8zebo=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=

แต่ละ dependency มี hash 2 บรรทัด:

  • h1:... — hash ของเนื้อหา module zip
  • /go.mod h1:... — hash ของเฉพาะไฟล์ go.mod

ทั้งคู่ต้องตรงกันทุกเครื่องและใน CI — go mod verify คอยตรวจให้ และคุณควร commit go.sum เข้า version control ด้วย (เหมือน package-lock.json)

Terminal window
# Initialise a new module
go mod init github.com/myorg/my-app
# Add a dependency (updates go.mod + go.sum)
go get github.com/gin-gonic/[email protected]
# Upgrade to latest patch/minor
go get github.com/gin-gonic/gin@latest
# Remove unused dependencies
go mod tidy
# Verify all downloaded modules match go.sum
go mod verify
# List all direct + indirect dependencies
go list -m all
# Show available versions of a module
go list -m -versions github.com/gin-gonic/gin
  • go mod init เลือกเวอร์ชันแบบระวังตัว (1.26+): toolchain Go 1.27.x จะเขียน go 1.26.0 ไม่ใช่ go 1.27.0 เพื่อไม่ให้ module ของคุณเผลอบังคับใช้รุ่นใหม่ล่าสุด อยากใช้ฟีเจอร์เฉพาะ 1.27 ก็แก้เป็น go 1.27 เอง — vet check stdversion จะคอยเตือนให้
  • directive ignore (1.25+): บอกคำสั่ง go ให้ข้ามโฟลเดอร์ที่ไม่ควรถูกมองเป็น package เช่น build output ของ frontend — .npmignore ในโลก module
  • require แบบสองบล็อก (1.27+, สำหรับ module ที่ระบุ go 1.27): go mod tidy รวมบล็อกซ้ำให้เหลือบล็อก direct dependency หนึ่งบล็อกและ // indirect อีกหนึ่งบล็อกพอดี โดยเก็บ comment ที่ติดอยู่ไว้
module github.com/myorg/my-app
go 1.27
ignore ./web/dist
require (
github.com/labstack/echo/v5 v5.3.1
)
require (
golang.org/x/net v0.45.0 // indirect
)

Go modules ฝัง major version ไว้ใน import path สำหรับ v2 ขึ้นไป นี่คือจุดที่ต่างจาก npm มากที่สุดจนหลายคนแปลกใจ:

TypeScript
// npm — same package name, just a new version
// package.json
"dependencies": {
"some-lib": "^3.0.0" // v3, same import
}
// import someLib from 'some-lib'; ← unchanged
Go
// Go — v2+ changes the import path
// go.mod
require github.com/some-lib/core v2.0.0+incompatible
// If the library has a proper /v2 module:
require github.com/some-lib/core/v2 v2.0.0
// Your imports change:
import "github.com/some-lib/core/v2"
// v1: import "github.com/some-lib/core"
// v2: import "github.com/some-lib/core/v2"

ตอนที่คุณ develop dependency ไปพร้อมกับ main module replace จะ map module path ไปยัง directory บนเครื่อง — เทียบเท่า npm link หรือ path แบบ file: ใน package.json:

go.mod
require github.com/myorg/shared-lib v1.2.3
replace github.com/myorg/shared-lib => ../shared-lib

ลบ replace ออกก่อน commit ขึ้น production แล้วรัน go mod tidy ตามเพื่อเก็บกวาดให้เรียบร้อย

Vendoring คือการ copy dependency ทั้งหมดมาไว้ใน directory vendor/ ภายใน repo — มีประโยชน์กับ build แบบ air-gapped หรือเวลาต้องการรับประกัน reproducibility โดยไม่ต้องพึ่ง module proxy:

Terminal window
# Create/update vendor directory
go mod vendor
# Build using only vendored deps (ignores module cache)
go build -mod=vendor ./...
# Verify vendor matches go.sum
go mod verify

Workspace (เพิ่มเข้ามาใน Go 1.18) ให้คุณ develop หลาย module ที่พึ่งพากันได้พร้อมกัน โดยไม่ต้องใช้ replace directive — เทียบเท่า npm workspaces หรือ yarn workspaces:

TypeScript
// npm workspaces — package.json at root
{
"workspaces": [
"packages/api",
"packages/shared"
]
}
Go
// Go workspaces — go.work at repo root
go 1.27
use (
./api // module: github.com/myorg/api
./shared // module: github.com/myorg/shared
)
// Now api can import shared directly from disk,
// no replace directive needed.
Terminal window
# Initialise a workspace (creates go.work)
go work init ./api ./shared
# Add another module to the workspace
go work use ./cli
# Sync go.work.sum (lock file for workspaces)
go work sync

ปกติ go.work มักถูก git-ignore เพราะเป็นของอำนวยความสะดวกระดับ local ไม่ใช่ deployment artefact ถ้าจำเป็นก็ใช้ GOWORK=off เพื่อปิดใน CI ได้

Go เทียบเท่า package-lock.json คืออะไร?
Go จัดการ v2+ breaking changes ใน module imports อย่างไร?
คำสั่งใดที่ลบ dependencies ที่ไม่ใช้ออกจาก go.mod และ go.sum?
จุดประสงค์ของ replace directive ใน go.mod คืออะไร?