در دنیای توسعه وب، نیاز به برنامههایی با عملکرد بالا و مقیاسپذیر روزبهروز افزایش مییابد. WebAssembly (WASM) بهعنوان یک فناوری نوظهور، امکان اجرای کدهای سطح پایین با سرعتی نزدیک به برنامههای نیتیو را در مرورگرها فراهم کرده است. وقتی این فناوری با زبان برنامهنویسی Go (Golang) ترکیب میشود، توسعهدهندگان میتوانند برنامههای وب قدرتمند و کارآمدی بسازند که نهتنها سریع هستند، بلکه از قابلیتهای همزمانی و تایپ استاتیک Go نیز بهره میبرند. در این مقاله، با اصول WebAssembly، نحوه استفاده از آن با Go، و ویژگیهای جدید معرفیشده در Go 1.24 آشنا میشوید. همچنین، یک مثال عملی از ساخت یک فرمتکننده JSON و یک نمونه واقعی از پروژه Permify ارائه خواهیم داد تا نشان دهیم چگونه این فناوریها در عمل به کار میروند.
WebAssembly چیست؟
WebAssembly یک فرمت دستورالعمل باینری است که بهعنوان یک ماشین مجازی سطح پایین عمل میکند. این فناوری برای اجرای کدهای کامپایلشده از زبانهای سطح بالا مانند Go، C++، و Rust طراحی شده است و عملکردی نزدیک به برنامههای نیتیو ارائه میدهد. برخلاف جاوااسکریپت که یک زبان تفسیری است، WebAssembly به دلیل ساختار باینری و نزدیکی به معماری سختافزار، برای برنامههای سنگین مانند بازیهای سهبعدی، ویرایشگرهای تصویر، و برنامههای واقعیت مجازی (VR/AR) ایدهآل است.
تاریخچه و اهمیت WebAssembly
WebAssembly در سال 2015 توسط کنسرسیوم W3C معرفی شد و از آن زمان به یکی از استانداردهای کلیدی توسعه وب تبدیل شده است. این فناوری در ابتدا برای بهبود عملکرد برنامههای وب در مرورگرها طراحی شد، اما اکنون در محیطهای غیرمرورگری مانند سرورها، بلاکچین، و حتی اینترنت اشیا (IoT) نیز کاربرد دارد. WebAssembly از طریق رابط سیستمی WebAssembly (WASI) امکان تعامل با منابع سیستمی را فراهم میکند، که آن را به گزینهای مناسب برای اجرای کد در محیطهای مختلف تبدیل کرده است.
چرا WebAssembly مهم است؟
جاوااسکریپت سالها تنها زبان قابلفهم برای مرورگرها بود. با وجود موتورهای قدرتمندی مانند V8 و Gecko که از کامپایل Just-in-Time (JIT) پشتیبانی میکنند، جاوااسکریپت همچنان در برنامههای پیچیده با محدودیتهای عملکردی مواجه است. WebAssembly این مشکلات را برطرف میکند:
- عملکرد بالا: کد WebAssembly به دلیل کامپایلشده بودن، سریعتر از جاوااسکریپت اجرا میشود.
- پشتیبانی گسترده: تمام مرورگرهای مدرن (کروم، فایرفاکس، سافاری، و اج) از WebAssembly پشتیبانی میکنند.
- همکاری با جاوااسکریپت: WebAssembly جایگزین جاوااسکریپت نیست، بلکه بهعنوان مکمل آن عمل میکند و امکان فراخوانی توابع بین این دو را فراهم میکند.
چرا از Go با WebAssembly استفاده کنیم؟
زبان Go به دلیل تایپ استاتیک، مدیریت همزمانی قوی (با استفاده از goroutines)، و عملکرد بهینه، گزینهای عالی برای کامپایل به WebAssembly است. ترکیب Go و WebAssembly امکان ایجاد برنامههای وب مقیاسپذیر و سریع را فراهم میکند.
مزایای استفاده از Go با WebAssembly
- عملکرد نزدیک به نیتیو: Go به دلیل کامپایل شدن به کد ماشین، عملکردی مشابه برنامههای نیتیو ارائه میدهد.
- مدیریت همزمانی: ویژگی goroutines امکان اجرای عملیات موازی در مرورگر را فراهم میکند.
- اکوسیستم قوی: Go ابزارها و کتابخانههای قدرتمندی برای کامپایل به WebAssembly ارائه میدهد.
- تجربه کاربری ساده: اجرای کد در مرورگر بدون نیاز به سرورهای پیچیده، تجربهای سریعتر به کاربران ارائه میدهد.
ویژگیهای جدید WebAssembly در Go 1.24
نسخه Go 1.24، منتشرشده در فوریه 2025، قابلیتهای WebAssembly را با معرفی دو ویژگی کلیدی بهبود بخشیده است: دستور go:wasmexport و پشتیبانی از WASI reactor. این ویژگیها امکان ساخت برنامههای WebAssembly انعطافپذیرتر و قدرتمندتر را فراهم میکنند.
دستور go:wasmexport
دستور go:wasmexport به توسعهدهندگان اجازه میدهد توابع Go را بهعنوان توابع صادرشده (exported) در ماژول WebAssembly تعریف کنند تا از میزبان (host) WebAssembly قابلفراخوانی باشند. این قابلیت مشابه دستور cgo برای صادرات توابع به C است، اما با مکانیزمی سادهتر.
مثال:
1//go:wasmexport add 2func add(a, b int32) int32 { 3 return a + b 4}
با افزودن دستور //go:wasmexport add، تابع add در ماژول WebAssembly بهعنوان یک تابع صادرشده در دسترس خواهد بود و میتواند از جاوااسکریپت یا سایر میزبانهای WebAssembly فراخوانی شود.
پشتیبانی از WASI Reactor
WASI (WebAssembly System Interface) یک رابط سیستمی است که امکان تعامل ماژولهای WebAssembly با منابع سیستمی را فراهم میکند. در Go 1.24، امکان ساخت یک WASI reactor با استفاده از پرچم -buildmode=c-shared معرفی شده است. برخلاف ماژولهای "command" که پس از اجرای تابع main خاتمه مییابند، یک reactor پس از مقداردهی اولیه فعال باقی میماند و توابع صادرشده آن میتوانند چندین بار فراخوانی شوند.
دستور کامپایل:
1GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o reactor.wasm
این دستور یک ماژول WebAssembly تولید میکند که بهجای تابع _start (نقطه ورود ماژولهای command)، یک تابع _initialize تولید میکند که باید قبل از فراخوانی توابع صادرشده اجرا شود.
مثال استفاده با Wazero (یک runtime WebAssembly مبتنی بر Go):
1package main 2 3import ( 4 "context" 5 "fmt" 6 "github.com/tetratelabs/wazero" 7 "github.com/tetratelabs/wazero/api" 8) 9 10func main() { 11 ctx := context.Background() 12 r := wazero.NewRuntime(ctx) 13 defer r.Close(ctx) 14 wasi_snapshot_preview1.MustInstantiate(ctx, r) 15 16 config := wazero.NewModuleConfig().WithStartFunctions("_initialize") 17 wasmModule, _ := r.InstantiateWithConfig(ctx, wasmFile, config) 18 19 fn := wasmModule.ExportedFunction("add") 20 var a, b int32 = 1, 2 21 res, _ := fn.Call(ctx, api.EncodeI32(a), api.EncodeI32(b)) 22 c := api.DecodeI32(res[0]) 23 fmt.Printf("add(%d, %d) = %d\n", a, b, c) 24}
پشتیبانی از انواع داده غنیتر
Go 1.24 محدودیتهای مربوط به انواع دادههای ورودی و خروجی در توابع go:wasmimport و go:wasmexport را کاهش داده است. اکنون میتوانید از انواع دادههایی مانند bool، string، اشارهگر به int32، یا ساختارهایی که شامل structs.HostLayout هستند استفاده کنید. این بهبود امکان نوشتن کدهای WebAssembly را بهصورت طبیعیتر و با تبدیلهای کمتری فراهم میکند.
محدودیتهای WebAssembly در Go 1.24
- تکرشتهای بودن: WebAssembly از معماری تکرشتهای استفاده میکند و goroutineهای پسزمینه پس از بازگشت تابع
go:wasmexportاجرا نمیشوند تا زمانی که ماژول دوباره فراخوانی شود. - محدودیتهای نوع داده: به دلیل تفاوت معماری 32 بیتی میزبان و 64 بیتی کلاینت، انتقال اشارهگرها به ساختارهای حاوی فیلدهای اشارهگر ممکن نیست.
- عملکرد goroutines: در حالی که Go از همزمانی پشتیبانی میکند، اجرای goroutines در WebAssembly به دلیل تکرشتهای بودن محدود است.
نمونه کاربردی: ساخت یک فرمتکننده JSON با Go و WebAssembly
برای درک بهتر، یک برنامه ساده Go را به WebAssembly کامپایل میکنیم که JSON ورودی را فرمت کرده و در مرورگر نمایش میدهد.
ساختار پروژه
ساختار دایرکتوری پروژه:
1mkdir -p ~/Documents/webassembly/assets \ 2~/Documents/webassembly/cmd \ 3~/Documents/webassembly/cmd/server \ 4~/Documents/webassembly/cmd/wasm
- assets: حاوی فایلهای HTML، جاوااسکریپت، و WebAssembly.
- cmd/server: سرور وب برای ارائه فایلها.
- cmd/wasm: کد Go برای کامپایل به WebAssembly.
- go.mod: فایل ماژول Go.
ایجاد ماژول Go:
1cd ~/Documents/webassembly 2go mod init github.com/yourusername/webassembly
برنامه Hello World
فایل main.go در ~/Documents/webassembly/cmd/wasm:
1package main 2 3import ( 4 "fmt" 5) 6 7func main() { 8 fmt.Println("Go WebAssembly: Hello World!") 9}
کامپایل به WebAssembly
1cd ~/Documents/webassembly/cmd/wasm/ 2GOOS=js GOARCH=wasm go build -o ../../assets/json.wasm
JavaScript Glue
فایل wasm_exec.js را کپی کنید:
1cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" ~/Documents/webassembly/assets/
فایل HTML
فایل index.html در ~/Documents/webassembly/assets:
1<!DOCTYPE html> 2<html> 3<head> 4 <meta charset="utf-8"/> 5 <title>WebAssembly with Go</title> 6 <script src="wasm_exec.js"></script> 7 <script> 8 const go = new Go(); 9 WebAssembly.instantiateStreaming(fetch("json.wasm"), go.importObject).then((result) => { 10 go.run(result.instance); 11 }); 12 </script> 13</head> 14<body> 15 <h1>JSON Formatter with WebAssembly and Go</h1> 16</body> 17</html>
سرور وب
فایل main.go در ~/Documents/webassembly/cmd/server:
1package main 2 3import ( 4 "fmt" 5 "net/http" 6) 7 8func main() { 9 err := http.ListenAndServe(":9090", http.FileServer(http.Dir("../../assets"))) 10 if err != nil { 11 fmt.Println("Failed to start server:", err) 12 return 13 } 14}
اجرا:
1cd ~/Documents/webassembly/cmd/server/ 2go run main.go
در مرورگر، http://localhost:9090 را باز کنید و در کنسول جاوااسکریپت، خروجی "Go WebAssembly: Hello World!" را مشاهده کنید.
توسعه فرمتکننده JSON
برای فرمت کردن JSON، فایل main.go در ~/Documents/webassembly/cmd/wasm را بهروزرسانی کنید:
1package main 2 3import ( 4 "encoding/json" 5 "fmt" 6 "syscall/js" 7) 8 9//go:wasmexport formatJSON 10func formatJSON(input string) (string, error) { 11 var raw interface{} 12 if err := json.Unmarshal([]byte(input), &raw); err != nil { 13 return "", err 14 } 15 pretty, err := json.MarshalIndent(raw, "", " ") 16 if err != nil { 17 return "", err 18 } 19 return string(pretty), nil 20} 21 22func jsonWrapper() js.Func { 23 jsonFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} { 24 if len(args) != 1 { 25 return "Invalid number of arguments" 26 } 27 inputJSON := args[0].String() 28 fmt.Printf("Input: %s\n", inputJSON) 29 pretty, err := formatJSON(inputJSON) 30 if err != nil { 31 fmt.Printf("Error converting to JSON: %s\n", err) 32 return err.Error() 33 } 34 return pretty 35 }) 36 return jsonFunc 37} 38 39func main() { 40 fmt.Println("Go WebAssembly: Hello World!") 41 js.Global().Set("formatJSON", jsonWrapper()) 42 <-make(chan struct{}) 43}
استفاده از go:wasmexport
با استفاده از ویژگی جدید Go 1.24، تابع formatJSON را با go:wasmexport صادر میکنیم تا مستقیماً از جاوااسکریپت قابلفراخوانی باشد.
تست
برنامه را کامپایل و اجرا کنید:
1cd ~/Documents/webassembly/cmd/wasm/ 2GOOS=js GOARCH=wasm go build -o ../../assets/json.wasm 3cd ~/Documents/webassembly/cmd/server/ 4go run main.go
در کنسول جاوااسکریپت مرورگر:
1formatJSON('{"website":"golangbot.com", "tutorials": [{"title": "Strings", "url":"/strings/"}]}')
خروجی:
1{ 2 "website": "golangbot.com", 3 "tutorials": [ 4 { 5 "title": "Strings", 6 "url": "/strings/" 7 } 8 ] 9}
نمونه واقعی: پروژه Permify
پروژه متنباز Permify از WebAssembly و Go برای ایجاد یک Playground تعاملی برای تست مدلهای مجوزدهی استفاده میکند.
5 تا از بدترین آنتی پترن ها در مدیریت API
مدیریت استیت در ری اکت با Zustand
پیادهسازی در Permify
۱. کامپایل به WebAssembly
1GOOS=js GOARCH=wasm go build -o permify.wasm main.go
۲. کد Go
1package main 2 3import ( 4 "context" 5 "encoding/json" 6 "syscall/js" 7 "google.golang.org/protobuf/encoding/protojson" 8 "github.com/Permify/permify/pkg/development" 9) 10 11var dev *development.Development 12 13//go:wasmexport run 14func run() js.Func { 15 return js.FuncOf(func(this js.Value, args []js.Value) interface{} { 16 var t interface{} 17 if err := json.Unmarshal([]byte(args[0].String()), &t); err != nil { 18 return js.ValueOf([]interface{}{"Invalid JSON"}) 19 } 20 input, ok := t.(map[string]interface{}) 21 if !ok { 22 return js.ValueOf([]interface{}{"Invalid JSON"}) 23 } 24 errors := dev.Run(context.Background(), input) 25 if len(errors) == 0 { 26 return js.ValueOf([]interface{}{}) 27 } 28 vs := make([]interface{}, 0, len(errors)) 29 for _, r := range errors { 30 result, err := json.Marshal(r) 31 if err != nil { 32 return js.ValueOf([]interface{}{err.Error()}) 33 } 34 vs = append(vs, string(result)) 35 } 36 return js.ValueOf(vs) 37 }) 38} 39 40func main() { 41 ch := make(chan struct{}, 0) 42 dev = development.NewContainer() 43 js.Global().Set("run", run()) 44 <-ch 45}
۳. ادغام با React
فایل wasmTypes.d.ts:
1declare global { 2 export interface Window { 3 Go: any; 4 run: (shape: string) => any[]; 5 } 6} 7export {};
فایل index.tsx:
1import React, { useEffect, useState } from "react"; 2import "./wasm_exec.js"; 3import "./wasmTypes.d.ts"; 4 5async function loadWasm(): Promise<void> { 6 const goWasm = new window.Go(); 7 const result = await WebAssembly.instantiateStreaming( 8 fetch("permify.wasm"), 9 goWasm.importObject 10 ); 11 goWasm.run(result.instance); 12} 13 14export const LoadWasm: React.FC<React.PropsWithChildren<{}>> = (props) => { 15 const [isLoading, setIsLoading] = useState(true); 16 17 useEffect(() => { 18 loadWasm().then(() => setIsLoading(false)); 19 }, []); 20 21 if (isLoading) { 22 return <div>Loading WebAssembly...</div>; 23 } 24 return <React.Fragment>{props.children}</React.Fragment>; 25};
۴. دکمه اجرا
1import React from "react"; 2 3type RunButtonProps = { 4 shape: string; 5 onResult: (result: any[]) => void; 6}; 7 8function RunButton({ shape, onResult }: RunButtonProps) { 9 const handleClick = async () => { 10 let result = await window.run(shape); 11 onResult(result); 12 }; 13 14 return <button onClick={handleClick}>Run WebAssembly</button>; 15}
نکات پیشرفته برای توسعهدهندگان
- بهینهسازی عملکرد:
-
از ابزار wasm-opt برای کاهش اندازه فایل WebAssembly استفاده کنید:
1wasm-opt json.wasm --enable-bulk-memory -Oz -o optimized.wasm -
از goroutines با احتیاط استفاده کنید، زیرا WebAssembly تکرشتهای است.
-
- مدیریت خطاها:
- همیشه خطاها را به جاوااسکریپت برگردانید تا تجربه کاربری بهتری فراهم شود.
- از ساختارهای JSON برای انتقال دادههای پیچیده استفاده کنید.
- تست و دیباگ:
- از کنسول مرورگر برای دیباگ استفاده کنید.
- ابزارهایی مانند Wazero برای تست ماژولهای WASI در محیطهای غیرمرورگری مفید هستند.
- استفاده از WASI:
- برای برنامههایی که نیاز به تعامل با منابع سیستمی دارند، از پورت wasip1 و build mode c-shared استفاده کنید.
نتیجه
ترکیب WebAssembly و Go، بهویژه با ویژگیهای جدید Go 1.24 مانند go:wasmexport و WASI reactor، امکان ساخت برنامههای وب قدرتمند و مقیاسپذیر را فراهم کرده است. در این مقاله، از یک مثال ساده فرمتکننده JSON تا کاربرد واقعی در پروژه Permify، نحوه استفاده از این فناوریها را بررسی کردیم. این ابزارها به توسعهدهندگان اجازه میدهند تا برنامههای پیچیده را مستقیماً در مرورگر اجرا کنند و تجربه کاربری بهتری ارائه دهند.
