كيف يمكن دمج WebAssembly في هذا الإعداد؟ في هذه المقالة، سنتعرف على ذلك باستخدام C/C++ وEmscripten كمثال.
وغالبًا ما يكون WebAssembly (wasm) قد تم تأطيرها إما كإجراء أساسي للأداء أو كطريقة لتشغيل لغة C++ الحالية على الويب. من خلال تطبيق squoosh.app، أردنا أن نعرض أن هناك منظورًا ثالثًا على الأقل لـ Wasm، وهو الاستفادة من والمنظومات المتكاملة للغات البرمجة الأخرى. مع Emscripten، يمكنك استخدام رمز C/C++ ، يوفّر تطبيق Rust إمكانية استخدام Wasm مع ميزة Go وفريقك على ذلك أيضًا. أنا التأكد من العديد من اللغات الأخرى.
في هذه السيناريوهات، لا يشكّل Wasm العنصر الأساسي في تطبيقك، بل هو مجرد ألغاز. هي وحدة أخرى. يحتوي تطبيقك على JavaScript أو CSS أو أصول الصور نظام إنشاء يركز على الويب وربما حتى إطار عمل مثل React. كيف دمج WebAssembly في هذا الإعداد؟ في هذه المقالة، سوف نعمل هذا باستخدام C/C++ وEmscripten كمثال.
Docker
أجد أن Docker لا يقدر بثمن عند العمل مع Emscripten. ملفات المصدر C/C++ غالبًا ما تتم كتابة المكتبات للعمل مع نظام التشغيل التي تم إنشاؤها عليه. من المفيد للغاية أن تكون لديك بيئة متسقة. باستخدام Docker، تحصل على لنظام Linux افتراضي تم إعداده بالفعل للعمل مع Emscripten ويتضمن جميع الأدوات والتبعيات المثبتة. في حالة فقد شيء ما، يمكنك فقط تثبيته دون القلق حيال كيفية تأثيره على جهازك أو للمشروعات الأخرى. إذا حدث خطأ ما، فتخلص من الحاوية وابدأ خلال. إذا كانت تعمل مرة واحدة، فيمكنك التأكد من أنها ستستمر في العمل تؤدي إلى نتائج متطابقة.
يحتوي Docker Registry على Emscripten صورة من إنشاء trzeci التي أستخدمها على نطاق واسع.
التكامل مع npm
في أغلب الحالات، تكون نقطة الدخول إلى مشروع الويب هي نقطة npm
package.json
وفقًا للمؤتمر، يمكن إنشاء معظم المشاريع باستخدام "npm install &&
npm run build
".
بشكل عام، عناصر التصميم التي تم إنتاجها من خلال Emscripten (السمة .js
و.wasm
) على أنها وحدة JavaScript أخرى
مادة العرض. يمكن التعامل مع ملف JavaScript بواسطة أداة تجميع مثل webpack أو الدمج،
ويجب التعامل مع ملف Wasm مثل أي أصل ثنائي آخر أكبر، مثل
الصور.
وبالتالي، يجب إنشاء عناصر إصدار Emscripten قبل القيم "العادية" بدء عملية الإنشاء:
{
"name": "my-worldchanging-project",
"scripts": {
"build:emscripten": "docker run --rm -v $(pwd):/src trzeci/emscripten
./build.sh",
"build:app": "<the old build command>",
"build": "npm run build:emscripten && npm run build:app",
// ...
},
// ...
}
يمكن أن تستدعي مهمة build:emscripten
الجديدة Emscripten مباشرةً، ولكن
ذكرته من قبل، فإنني أوصي باستخدام Docker للتأكد من توفير بيئة
متسقة.
"docker run ... trzeci/emscripten ./build.sh
" يطلب من Docker أن يدور
باستخدام الصورة trzeci/emscripten
ونفِّذ الأمر ./build.sh
.
build.sh
هو نص برمجي ستكتبه تاليًا. يقول --rm
:
Docker لحذف الحاوية بعد اكتمال تشغيلها. بهذه الطريقة، لا تنشئ
مجموعة من صور الأجهزة القديمة بمرور الوقت. -v $(pwd):/src
تعني أن
تريد من Docker "إجراء نسخ مطابق" الدليل الحالي ($(pwd)
) إلى /src
داخله
الحاوية. أي تغييرات تجريها على الملفات في دليل /src
داخل الدليل
مع مشروعك الفعلي. هذه الأدلة التي تتم مزامنتها على الجهاز وفي السحابة الإلكترونية
تسمى "حوامل الربط".
لنلقِ نظرة على build.sh
:
#!/bin/bash
set -e
export OPTIMIZE="-Os"
export LDFLAGS="${OPTIMIZE}"
export CFLAGS="${OPTIMIZE}"
export CXXFLAGS="${OPTIMIZE}"
echo "============================================="
echo "Compiling wasm bindings"
echo "============================================="
(
# Compile C/C++ code
emcc \
${OPTIMIZE} \
--bind \
-s STRICT=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s MALLOC=emmalloc \
-s MODULARIZE=1 \
-s EXPORT_ES6=1 \
-o ./my-module.js \
src/my-module.cpp
# Create output folder
mkdir -p dist
# Move artifacts
mv my-module.{js,wasm} dist
)
echo "============================================="
echo "Compiling wasm bindings done"
echo "============================================="
هناك الكثير لتحليله هنا!
set -e
يضع الصدفة على "إخفاق سريع" الحالي. إذا كانت هناك أي أوامر في النص البرمجي
خطأ، فسيتم إلغاء النص البرمجي بأكمله على الفور. يمكن أن تظهر فائدة هذا بوضوح أكبر
مفيدة للغاية حيث سيكون المخرج الأخير للنص البرمجي ناجحًا دائمًا
أو الخطأ الذي تسبّب في إخفاق الإصدار.
باستخدام عبارات export
، يمكنك تحديد قيم اثنين من البيئات
المتغيرات. فهي تسمح لك بتمرير معلمات سطر أوامر إضافية إلى واجهة برمجة التطبيقات C
والمحول (CFLAGS
) والمحول البرمجي لـ C++ (CXXFLAGS
) والرابط (LDFLAGS
).
فجميعها تتلقى إعدادات المحسِّن عبر OPTIMIZE
للتأكد من
يتم تحسين كل شيء بنفس الطريقة. هناك قيمتان محتملتان
للمتغيّر OPTIMIZE
:
-O0
: عدم إجراء أي تحسين لا يتم التخلص من أي رموز برمجية غير صالحة وEmscripten لا يقوم بتصغير رمز JavaScript الذي يصدره، جيد لتصحيح الأخطاء.-O3
: تُجري تحسينًا كبيرًا لتحقيق الأداء.-Os
: تحسين الأداء بشكل كبير لتحسين الأداء والحجم كعنصر ثانوي معيار ما.-Oz
: يمكنك تحسين الحجم بشكل كبير، والتضحية بالأداء عند الضرورة.
أنصحك باستخدام التطبيق -Os
في الغالب على الويب.
يحتوي الأمر emcc
على عدد لا يُحصى من الخيارات الخاصة به. لاحظ أن emcc
من المفترض أن تكون "بديلاً لبرامج التحويل البرمجي مثل GCC أو clang". إذًا، جميعًا
من المرجح أن يتم تنفيذ العلامات التي قد تعرفها من GCC بواسطة emcc
أيضًا. علامة -s
هي ميزة خاصة من حيث أنّها تتيح لنا ضبط Emscripten
على وجه التحديد. يمكن العثور على جميع الخيارات المتاحة في ملف Emscripten.
settings.js
،
ولكن هذا الملف قد يكون مربكًا جدًا. في ما يلي قائمة بعلامات Emscripten
والتي أعتقد أنّها الأكثر أهمية لمطوّري الويب:
- تفعيل
--bind
دمج. - سيُلغي
-s STRICT=1
التوافق مع جميع خيارات الإصدار المتوقّفة نهائيًا. ويضمن ذلك التي تبنيها التعليمات البرمجية بطريقة متوافقة مع الأمام. - يسمح
-s ALLOW_MEMORY_GROWTH=1
بتنمية الذاكرة تلقائيًا إذا اللازمة. أثناء الكتابة، سيخصّص Emscripten ذاكرة بحجم 16 ميغابايت. في البداية. نظرًا لأن التعليمة البرمجية تقوم بتخصيص أجزاء من الذاكرة، فإن هذا الخيار يقرر ما إذا هذه العمليات إلى فشل وحدة Wasm بالكامل عند تشغيل الذاكرة أو إذا تم السماح لشفرة الغراء بتوسيع إجمالي الذاكرة إلى تتلاءم مع التخصيص. - تختار الدالة
-s MALLOC=...
طريقة تنفيذmalloc()
التي سيتم استخدامها.emmalloc
هو عملية تنفيذmalloc()
صغيرة وسريعة خصّيصًا من أجل Emscripten. تشير رسالة الأشكال البيانية البديل هوdlmalloc
، وهو تنفيذ شامل لـmalloc()
. أنت فقط يلزم التبديل إلىdlmalloc
في حال تخصيص الكثير من العناصر الصغيرة بشكل متكرر أو إذا كنت تريد استخدام سلاسل المحادثات. - ستحوِّل
-s EXPORT_ES6=1
رمز JavaScript إلى وحدة ES6 باستخدام التصدير الافتراضي الذي يعمل مع أي أداة تجميع. يتطلب أيضًا-s MODULARIZE=1
من أجل تعيينه.
العلامات التالية ليست ضرورية دائمًا أو تكون مفيدة فقط لتصحيح الأخطاء الأغراض:
-s FILESYSTEM=0
هي علامة تتعلق بـ Emscripten وإمكانية على لمحاكاة نظام ملفات عندما يستخدم رمز C/C++ عمليات نظام الملفات. ويقوم ببعض التحليل للتعليمة البرمجية التي يقوم بتجميعها لتحديد ما إذا كان ينبغي تضمين نظام الملفات في الرمز الملتصق أم لا. ومع ذلك، في بعض الأحيان، فقد يخطئ التحليل في حساب قيمة كبيرة، وبالتالي تدفع 70 كيلوبايت لمحاكاة نظام الملفات، والتي قد لا تحتاج إليها. باستخدام-s FILESYSTEM=0
، يمكنك فرض عدم تضمين هذا الرمز في Emscripten.- سيجعل
-g4
Emscripten يتضمن معلومات تصحيح الأخطاء في.wasm
إرسال ملف خرائط مصدر لوحدة Wasm. يمكنك قراءة المزيد على تصحيح الأخطاء باستخدام Emscripten في عملية تصحيح الأخطاء .
وهذا كل ما في الأمر! لنختبر جهاز my-module.cpp
صغير جدًا:
#include <emscripten/bind.h>
using namespace emscripten;
int say_hello() {
printf("Hello from your wasm module\n");
return 0;
}
EMSCRIPTEN_BINDINGS(my_module) {
function("sayHello", &say_hello);
}
وindex.html
:
<!doctype html>
<title>Emscripten + npm example</title>
Open the console to see the output from the wasm module.
<script type="module">
import wasmModule from "./my-module.js";
const instance = wasmModule({
onRuntimeInitialized() {
instance.sayHello();
}
});
</script>
(إليك سجلّ يحتوي على جميع الملفات).
لبناء كل شيء، قم بتشغيل
$ npm install
$ npm run build
$ npm run serve
ومن المفترض أن يعرض لك الانتقال إلى localhost:8080 الإخراج التالي في وحدة تحكّم أدوات مطوّري البرامج:
![أدوات مطوّري البرامج تعرض رسالة مطبوعة باستخدام لغة C++ وEmscripten](https://web.dev/static/articles/emscripten-npm/image/devtools-showing-message-6e40fef465a41.png?authuser=7&hl=ar)
إضافة رمز C/C++ كتبعية
إذا كنت ترغب في إنشاء مكتبة C/C++ لتطبيق الويب الخاص بك، فيجب أن يكون كودها
جزء من مشروعك. يمكنك إضافة الرمز إلى مستودع مشروعك يدويًا
أو يمكنك استخدام npm لإدارة هذا النوع من التبعيات أيضًا. لنفترض أنني
أريد استخدام libvpx في تطبيق الويب لديّ. libvpx
هي مكتبة C++ لترميز الصور باستخدام VP8، وهو برنامج الترميز المستخدَم في ملفات .webm
.
مع ذلك، libvpx ليست ضمن npm ولا تحتوي على package.json
، لذا لا يمكنني
بتثبيته باستخدام npm مباشرةً.
وللتخلص من هذا اللغز، هناك
napa. napa تتيح لك تثبيت أي git
عنوان URL للمستودع كملحق في مجلد node_modules
.
تثبيت napa كتبعية:
$ npm install --save napa
وتأكَّد من تشغيل "napa
" كنص برمجي للتثبيت:
{
// ...
"scripts": {
"install": "napa",
// ...
},
"napa": {
"libvpx": "git+https://github.com/webmproject/libvpx"
}
// ...
}
عندما تشغّل npm install
، تتولى napa مهمة استنساخ libvpx GitHub
المستودع في node_modules
باسم libvpx
.
يمكنك الآن توسيع النص البرمجي للإصدار لإنشاء libvpx. تستخدم libvpx السمة configure
وmake
التي سيتم بناؤها. لحسن الحظ، يمكن أن تساعد Emscripten في ضمان أن يكون configure
يستخدم make
المحول البرمجي لـ Emscripten. ولهذا الغرض، يوجد برنامج تضمين
الأمران emconfigure
وemmake
:
# ... above is unchanged ...
echo "============================================="
echo "Compiling libvpx"
echo "============================================="
(
rm -rf build-vpx || true
mkdir build-vpx
cd build-vpx
emconfigure ../node_modules/libvpx/configure \
--target=generic-gnu
emmake make
)
echo "============================================="
echo "Compiling libvpx done"
echo "============================================="
echo "============================================="
echo "Compiling wasm bindings"
echo "============================================="
# ... below is unchanged ...
تنقسم مكتبة C/C++ إلى جزأين: العناوين (عادةً .h
أو
.hpp
) التي تحدد هياكل البيانات والفئات والثوابت وما إلى ذلك، والتي
والمكتبة الفعلية (عادةً ما تكون الملفات .so
أو .a
). إلى
استخدم ثابت VPX_CODEC_ABI_VERSION
للمكتبة في التعليمات البرمجية، لديك
لتضمين ملفات رؤوس المكتبة باستخدام عبارة #include
:
#include "vpxenc.h"
#include <emscripten/bind.h>
int say_hello() {
printf("Hello from your wasm module with libvpx %d\n", VPX_CODEC_ABI_VERSION);
return 0;
}
المشكلة هي أن برنامج التحويل البرمجي لا يعرف أين يبحث عن vpxenc.h
.
هذا هو الهدف من العلامة -I
. تخبر المحول البرمجي بالأدلة
تحقق من ملفات العناوين. بالإضافة إلى ذلك، تحتاج أيضًا إلى إعطاء المحول البرمجي
ملف المكتبة الفعلي:
# ... above is unchanged ...
echo "============================================="
echo "Compiling wasm bindings"
echo "============================================="
(
# Compile C/C++ code
emcc \
${OPTIMIZE} \
--bind \
-s STRICT=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s ASSERTIONS=0 \
-s MALLOC=emmalloc \
-s MODULARIZE=1 \
-s EXPORT_ES6=1 \
-o ./my-module.js \
-I ./node_modules/libvpx \
src/my-module.cpp \
build-vpx/libvpx.a
# ... below is unchanged ...
في حال تشغيل npm run build
الآن، ستلاحظ أنّ هذه العملية تنشئ .js
جديدًا.
وملف .wasm
جديد وأن الصفحة التجريبية ستنتج بالفعل القيمة الثابتة:
![DevTools
تعرض نسخة ABI من libvpx مطبوعة بواسطة النص.](https://web.dev/static/articles/emscripten-npm/image/devtools-showing-the-abi-ab9a0f24d2c5c.png?authuser=7&hl=ar)
ستلاحظ أيضًا أن عملية التصميم تستغرق وقتًا طويلاً. سبب
قد تتفاوت مدد الإنشاء الطويلة. في حالة libvpx، يستغرق الأمر وقتًا طويلاً لأنه
فهو يجمع برنامج ترميز وبرنامج فك ترميز لكل من VP8 وVP9 في كل مرة يتم فيها تشغيل
أمر الإنشاء، على الرغم من أن ملفات المصدر لم تتغير. حتى لو لم يكن
سيستغرق إنشاء التغيير على my-module.cpp
وقتًا طويلاً. سيكون الأمر كبيرًا
من المفيد الحفاظ على عناصر libvpx الأساسية بعد
بإنشائه لأول مرة.
وتتمثل إحدى طرق تحقيق ذلك في استخدام متغيرات البيئة.
# ... above is unchanged ...
eval $@
echo "============================================="
echo "Compiling libvpx"
echo "============================================="
test -n "$SKIP_LIBVPX" || (
rm -rf build-vpx || true
mkdir build-vpx
cd build-vpx
emconfigure ../node_modules/libvpx/configure \
--target=generic-gnu
emmake make
)
echo "============================================="
echo "Compiling libvpx done"
echo "============================================="
# ... below is unchanged ...
(إليك السجلّ يتضمن جميع الملفات).
يتيح لنا الأمر eval
ضبط متغيرات البيئة من خلال تمرير المعلَمات
إلى النص البرمجي للإصدار. سيتخطى الأمر test
إنشاء libvpx إذا
تم ضبط $SKIP_LIBVPX
(على أي قيمة).
يمكنك الآن تجميع الوحدة الخاصة بك مع تخطّي إعادة إنشاء libvpx:
$ npm run build:emscripten -- SKIP_LIBVPX=1
تخصيص بيئة التصميم
تعتمد المكتبات أحيانًا على أدوات إضافية للإنشاء. إذا كانت هذه التبعيات
مفقودة في بيئة الإنشاء التي توفرها صورة Docker، يجب إجراء ما يلي:
إضافتها بنفسك. على سبيل المثال، لنفترض أنك تريد أيضًا إنشاء مخطط
لتوثيق libvpx باستخدام doxygen. دوكسجين
المتاحة في حاوية Docker، ولكن يمكنك تثبيتها باستخدام apt
.
إذا نفّذت هذا الإجراء في build.sh
، عليك إعادة تنزيله ثم إعادة تثبيته.
دوكسجين في كل مرة تريد فيها بناء مكتبتك. لن يكون ذلك فحسب
مهدر، ولكنه سيمنعك أيضًا من العمل في مشروعك أثناء عدم الاتصال بالإنترنت.
من المنطقي هنا إنشاء صورة Docker خاصة بك. يتم إنشاء صور Docker بواسطة
كتابة Dockerfile
تصف خطوات التصميم. تكون ملفات Dockerfiles
قوية ولديك الكثير من
والأوامر، ولكن معظم
الذي يمكنك الاستفادة منه بمجرد استخدام FROM
وRUN
وADD
. في هذه الحالة:
FROM trzeci/emscripten
RUN apt-get update && \
apt-get install -qqy doxygen
من خلال FROM
، يمكنك تحديد صورة Docker التي تريد استخدامها كبداية
نقطة واحدة. لقد اخترت trzeci/emscripten
كأساس، الصورة التي كنت تستخدمها
طوال الوقت. باستخدام RUN
، يمكنك توجيه Docker لتنفيذ أوامر واجهة الأوامر ضمن
. مهما كانت التغييرات التي تجريها هذه الأوامر على الحاوية هي الآن جزء من
صورة Docker. للتأكد من إنشاء صورة Docker
المتاحة قبل تشغيل build.sh
، يجب تعديل package.json
بت:
{
// ...
"scripts": {
"build:dockerimage": "docker image inspect -f '.' mydockerimage || docker build -t mydockerimage .",
"build:emscripten": "docker run --rm -v $(pwd):/src mydockerimage ./build.sh",
"build": "npm run build:dockerimage && npm run build:emscripten && npm run build:app",
// ...
},
// ...
}
(إليك السجلّ يتضمن جميع الملفات).
سيؤدي هذا إلى إنشاء صورة Docker، ولكن فقط إذا لم يكن قد تم إنشاؤها بعد. بَعْدَ ذَلِكْ
كل شيء يعمل كما كان من قبل، ولكن أصبحت بيئة الإصدار الآن تحتوي على doxygen
الأمر المتاح، الأمر الذي سيؤدي إلى إنشاء وثائق libvpx
أيضًا.
الخاتمة
ليس من المستغرب أن تكون رموز C/C++ وnpm غير مناسبة بشكل طبيعي، ولكن يمكنك تجعلها تعمل بشكل مريح تمامًا باستخدام بعض الأدوات الإضافية وأداة التي توفرها Docker. لن يصلح هذا الإعداد لكل مشروع، ولكنه نقطة انطلاق يمكنك تعديلها لتلبية احتياجاتك. إذا كان لديك التحسينات، لذا يُرجى مشاركتها.
الملحق: الاستفادة من طبقات صور Docker
هناك حل بديل وهو تغليف المزيد من هذه المشكلات باستخدام Docker طريقة Docker الذكية في التخزين المؤقت تنفذ Docker ملفات Dockerfiles خطوة بخطوة وتعين نتيجة كل خطوة صورة خاصة به. هذه الصور الوسيطة تسمى "الطبقات". إذا لم يتغير أمر في ملف Dockerfile، فإن Docker لن يعيد تشغيل هذه الخطوة في الواقع عند إعادة إنشاء الملف الشامل. بدلاً من ذلك يعيد استخدام الطبقة التي تم إنشاؤها من آخر مرة تم إنشاء الصورة فيها.
في السابق، كان عليك بذل بعض الجهود لعدم إعادة إنشاء libvpx في كل مرة.
في إنشاء تطبيقك. يمكنك بدلاً من ذلك نقل تعليمات إنشاء libvpx
من build.sh
إلى Dockerfile
للاستفادة من التخزين المؤقت في Docker
الآلية:
FROM trzeci/emscripten
RUN apt-get update && \
apt-get install -qqy doxygen git && \
mkdir -p /opt/libvpx/build && \
git clone https://github.com/webmproject/libvpx /opt/libvpx/src
RUN cd /opt/libvpx/build && \
emconfigure ../src/configure --target=generic-gnu && \
emmake make
(إليك السجلّ يتضمن جميع الملفات).
تجدر الإشارة إلى أنّك تحتاج إلى تثبيت git وclone libvpx يدويًا بما أنّه ليس لديك
ربط عمليات التثبيت عند تشغيل docker build
كأثر جانبي، لا توجد حاجة إلى
napa بعد الآن.