لماذا تنهار تطبيقات Wails في بيئة الإنتاج وكيفية التحكم بها على مستوى منخفض
26 июля 2026 г.
0
Computing/SoftwareComments (0)
Log in to leave a comment
No posts yet
Log in to leave a comment
No posts yet
عند تطوير تطبيقات سطح المكتب باستخدام Go، يُعد Wails خيارًا جذابًا للغاية. فهو لا يقوم بحزم Chromium بالكامل كما يفعل Electron، مما يجعله خفيف الوزن وسريعًا. ولكن بمجرد الخروج من نطاق البرنامج التعليمي ومحاولة بناء خدمة حقيقية، ستصطدم بالحائط مباشرةً؛ حيث تبدأ ذاكرة CGo بالتسرب، ويتصرف عرض الويب (WebView) بشكل مختلف بين نظامي Windows وmacOS.
بالنسبة لمطوري الخلفية (Backend) الذين يفتقرون إلى الخبرة في التعامل مع C/C++ أو Objective-C، تُعتبر هذه المرحلة بمثابة عقبة كؤود. وإذا لم تتمكن من السيطرة على تسريبات الذاكرة وتشتت سلوك عرض الويب عبر أنظمة التشغيل المختلفة والمختفية خلف الأمثلة البراقة في التوثيق الرسمي، فإن النشر في بيئة الإنتاج سيكون أمرًا مستحيلاً.
أكثر الافتراضات الخاطئة شائعة عند استخدام CGo هي توقع أن جامع القمامة (Garbage Collector) الخاص بـ Go سيتكفل بمنطقة C أيضًا. بالطبع، لن يفعل ذلك. فالذاكرة المخصصة عبر C.CString أو C.malloc تظل في منطقة C وتستهلك الذاكرة حتى ينتهي التطبيق.
يجب الحذر أيضًا عند تمرير شرائح Go (Go Slices) إلى دالات C. إذ إن تمرير عنوان رأس الشريحة نفسه يتسبب في تلف الذاكرة. والأسلوب الآمن هو تمرير unsafe.Pointer(&slice[0]) وهو العنوان الفعلي للأنصر الأول. وعند استدعاء كود Objective-C في macOS، لا ينبغي الاعتماد على ARC؛ حيث تظل الكائنات المنشأة داخل حلقة خيط CGo تتراكم في NSAutoreleasePool. يجب إحاطتها بشكل صريح بكتلة @autoreleasepool { ... } لتفريغها أولاً بأول.
في بيئة Windows، لا داعي لحمل مترجم CGo (مثل MinGW) كعبء إضافي. يمكنك ببساطة استدعاء ملفات DLL مباشرة باستخدام حزمة syscall دون تكبد حمولة CGo الزائدة. الكود الخاص باستدعاء dwmapi.dll لتفعيل الوضع الداكن أسهل مما تتخيل.
`go
// system_windows.go
//go:build windows
package native
import (
"syscall"
"unsafe"
)
var (
modDwmApi = syscall.NewLazyDLL("dwmapi.dll")
procDwmSetWindowAttribute = modDwmApi.NewProc("DwmSetWindowAttribute")
)
const DWMWA_USE_IMMERSIVE_DARK_MODE = 20
func SetWindowsDarkMode(hwnd uintptr, enable bool) error {
var val int32
if enable {
val = 1
}
ret, _, err := procDwmSetWindowAttribute.Call(
hwnd,
uintptr(DWMWA_USE_IMMERSIVE_DARK_MODE),
uintptr(unsafe.Pointer(&val)),
uintptr(unsafe.Sizeof(val)),
)
if ret != 0 {
return err
}
return nil
}
`
عند إنشاء وحدة تحكم أصيلة (Native Control Module)، ابدأ بتحديد واجهة مشتركة (system_interface.go). ثم في تنفيذ macOS (system_darwin.go)، ضع منطق Objective-C مع توجيه //go:build darwin، وفي تنفيذ Windows، اكتب Pure-Go Syscall مع //go:build windows للفصل بينهما. وبمجرد التعود على إرفاق defer C.free فور تخصيص CGo، لن يتوقف تطبيقك عن العمل بسبب تسرب الذاكرة.
يستخدم Wails محرك عرض الويب المثبت مسبقًا في نظام التشغيل: WebKit (Safari) على macOS، و WebView2 (Chromium) على Windows. ومقابل تقليل حجم الملف التنفيذي إلى نحو 15 ميجابايت، يتوجب عليك التعامل مع تشتت محركات المتصفحات بنفسك.
على سبيل المثال، عند تحديد منطقة السحب في نافذة بدون إطار (Frameless Window)، يعمل WebView2 بمجرد إضافة --wails-draggable: drag، بينما لن تتحرك النافذة في WebKit ما لم تحدد -webkit-app-region: drag أيضًا.
تحدث المشكلات أيضًا في معالجة الأحداث. فإذا قام Go goroutine بإنشاء آلاف الأحداث في الثانية باستخدام runtime.EventsEmit، ستعاني خيط الواجهة الموحد في عرض الويب ويتجمد المشهد. يجب وضع مخزن مؤقت (Buffer) في الخلفية لتقليل وتيرة الأحداث (Throttling) بآلية 60 إطارًا في الثانية (حوالي 16 مللي ثانية). كما ينبغي منع المواقف غير المرغوبة في الواجهة الأمامية مثل إعادة تحميل الصفحة عند ضغط المستخدم على F5 أو ظهور قائمة الزر الأيمن عبر إصلاحات عامة.
`typescript
// eventPatch.ts
export function applyGlobalUIFixes() {
window.addEventListener('contextmenu', (e: MouseEvent) => {
const target = e.target as HTMLElement;
if (target.tagName !== 'INPUT' && target.tagName !== 'TEXTAREA') {
e.preventDefault();
}
});
window.addEventListener('keydown', (e: KeyboardEvent) => {
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
const modifier = isMac ? e.metaKey : e.ctrlKey;
if (e.key === 'F5' || (modifier && e.key.toLowerCase() === 'r')) {
e.preventDefault();
e.stopPropagation();
}
});
}
`
في CSS، قم بتضمين خصائص السحب لكلا المحركين في نفس الوقت، وقم بتنفيذ applyGlobalUIFixes() في نقطة دخول التطبيق (main.ts أو App.tsx). مع إضافة مؤقت للتحكم بالوتيرة في الجزء الخاص بإنشاء الأحداث بالخلفية. بهذه الآليات البسيطة، يمكن التعامل مع معظم السلوكيات الغريبة الناتجة عن خصائص WebView في كل نظام تشغيل.
تستهلك تطبيقات Wails عادةً ما بين 35 إلى 50 ميجابايت من ذاكرة RAM، وهو رقم ممتاز مقارنةً بـ Electron الذي يلتهم أكثر من 200 ميجابايت. تظهر المشكلة الحقيقية عند نقل البيانات الضخمة أو البيانات الثنائية (Binary Data) إلى الواجهة الأمامية.
عند تبادل بيانات بحجم 50 ميجابايت باستخدام ربط JSON RPC الافتراضي، يرتفع استهلاك RAM فجأة إلى أكثر من 180 ميجابايت أثناء عملية تسلسل JSON. لتجنب هذه الظاهرة، يجب استخدام الخيار AssetServer.AssetsHandler لتنفيذ بث HTTP مخصص. وبنقل البيانات بأسلوب Zero-copy الخالي من نسخ الذاكرة، يمكنك إبقاء استهلاك الذاكرة في حالة الخمول والعمل محصورًا بين 22 و 30 ميجابايت.
يتطلب الأمر أيضًا الاستجابة لبيئات مستخدمي Windows. بالنسبة للعملاء الذين لم يتم تثبيت WebView2 Runtime لديهم، يمكنك تضمين أداة التثبيت الإضافية (Bootstrapper) أثناء البناء باستخدام الخيار -webview2 download.
`yaml
name: Multiplatform Release Build
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: macos-latest
platform: darwin/universal
output_name: OptimizedApp-macOS-Universal
- os: windows-latest
platform: windows/amd64
output_name: OptimizedApp-Windows-Installer
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Wails
run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
- name: Build macOS Universal Binary
if: runner.os == 'macOS'
run: wails build -platform darwin/universal -clean
- name: Build Windows Installer
if: runner.os == 'Windows'
run: |
choco install nsis -y
wails build -platform windows/amd64 -nsis -webview2 download -clean
`
في main.go، قم بربط http.Handler مخصص بـ AssetServer.AssetsHandler لحل مشكلة قفزات الذاكرة عند التعامل مع البيانات الضخمة. ثم أضف خط الأنابيب أعلاه إلى .github/workflows/release.yml. بهذا الهيكل، سيتم إنشاء ملفات macOS الموحدة وتطبيقات تثبيت Windows NSIS ورفعها إلى GitHub Releases في كل مرة تقوم فيها برفع Tag جديد.
من خلال الاهتمام بـ إدارة الذاكرة على مستوى منخفض بنفسك وامتصاص الفروق بين محركات عرض الويب عبر الكود، يمكنك بناء تطبيقات سطح مكتب صلبة ومستقرة باستخدام Wails.