استخدام Scala.js مع NPM و Browserify
نشرت: 2022-03-11إذا كنت تستخدم Scala.js ، مترجم لغة Scala إلى JavaScript ، فقد تجد أن إدارة التبعية القياسية لـ Scala.js محدودة للغاية في عالم JavaScript الحديث. يدير Scala.js التبعيات باستخدام WebJars ، بينما يدير مطورو JavaScript التبعيات باستخدام NPM. نظرًا لأن التبعيات التي تنتجها NPM هي من جانب الخادم ، فعادة ما تكون هناك حاجة إلى خطوة إضافية باستخدام Browserify أو Webpack لإنشاء رمز المتصفح.
في هذا المنشور ، سأصف كيفية دمج Scala.js مع عدد كبير من وحدات JavaScript المتاحة على NPM. يمكنك التحقق من مستودع GitHub هذا للحصول على مثال عملي للتقنيات الموضحة هنا. باستخدام هذا المثال وقراءة هذا المنشور ، ستتمكن من جمع مكتبات JavaScript الخاصة بك باستخدام NPM ، وإنشاء حزمة باستخدام Browserify ، واستخدام النتيجة في مشروع Scala.js الخاص بك. كل هذا دون تثبيت Node.js ، حيث تتم إدارة كل شيء بواسطة SBT.
إدارة التبعيات لـ Scala.js
اليوم ، أصبحت كتابة التطبيقات باللغات التي تُترجم إلى JavaScript ممارسة شائعة جدًا. ينتقل المزيد والمزيد من الأشخاص إلى لغات جافا سكريبت الموسعة ، مثل CoffeeScript أو TypeScript ، أو transpilers مثل Babel ، والتي تتيح لك استخدام ES6 اليوم. في الوقت نفسه ، تُستخدم مجموعة أدوات الويب من Google (مترجم من Java إلى JavaScript) في الغالب لتطبيقات المؤسسات. لهذه الأسباب ، بصفتي مطورًا لـ Scala ، لم أعد أعتبر استخدام Scala.js خيارًا غريبًا. المترجم سريع ، الشفرة المنتجة فعالة ، وبشكل عام هي مجرد طريقة لاستخدام نفس اللغة في كل من الواجهة الأمامية والخلفية.
ومع ذلك ، فإن استخدام أدوات Scala في عالم JavaScript ليس طبيعيًا بنسبة 100٪. يتعين عليك أحيانًا ملء الفراغ من نظام JavaScript البيئي إلى نظام Scala. تتمتع Scala.js كلغة بإمكانية تشغيل تفاعلي ممتازة مع JavaScript. نظرًا لأن Scala.js مترجم للغة Scala إلى JavaScript ، فمن السهل جدًا واجهة رمز Scala مع كود JavaScript الموجود. الأهم من ذلك ، يمنحك Scala.js القدرة على إنشاء واجهات مكتوبة (أو واجهات) للوصول إلى مكتبات JavaScript غير مكتوبة (على غرار ما تفعله باستخدام TypeScript). بالنسبة للمطورين المعتادين على اللغات المكتوبة بقوة مثل Java أو Scala أو حتى Haskell ، فإن JavaScript مكتوب بشكل فضفاض للغاية. إذا كنت مطورًا كهذا ، فربما يكون السبب الرئيسي هو أنك قد ترغب في استخدام Scala.js هو الحصول على لغة مكتوبة (بقوة) فوق لغة غير مطبوعة.
هناك مشكلة في سلسلة أدوات Scala.js القياسية ، والتي تستند إلى SBT ولا تزال مفتوحة إلى حد ما ، وهي: كيف يتم تضمين التبعيات ، مثل مكتبات JavaScript الإضافية ، في مشروعك؟ تقوم SBT بتوحيد معايير WebJars ، لذلك من المفترض أن تستخدم WebJars لإدارة تبعياتك. لسوء الحظ ، في تجربتي ثبت أنها غير كافية.
المشكلة مع WebJars
كما ذكرنا ، فإن طريقة Scala.js القياسية لاسترداد تبعيات JavaScript تعتمد على WebJars. سكالا ، بعد كل شيء ، لغة JVM. يستخدم Scala.js SBT لبناء البرامج بشكل أساسي ، وأخيرًا يعتبر SBT ممتازًا في إدارة تبعيات JAR.
لهذا السبب ، تم تحديد تنسيق WebJar بالضبط لاستيراد تبعيات JavaScript في عالم JVM. WebJar هو ملف JAR يشتمل على أصول الويب ، حيث يتضمن ملف JAR البسيط فئات Java المترجمة فقط. لذا ، فإن فكرة Scala.js هي أنه يجب عليك استيراد تبعيات JavaScript الخاصة بك عن طريق إضافة تبعيات WebJar ، بطريقة مماثلة يضيف Scala تبعيات JAR.
فكرة جميلة ، إلا أنها لا تعمل
أكبر مشكلة في Webjars هي أن إصدارًا عشوائيًا في مكتبة JavaScript عشوائية نادرًا ما يكون متاحًا كجهاز WebJar. في الوقت نفسه ، تتوفر الغالبية العظمى من مكتبات JavaScript كوحدات نمطية NPM. ومع ذلك ، هناك جسر مفترض بين NPM و WebJars باستخدام أداة npm-to-webjar
. لذلك حاولت استيراد مكتبة ، متوفرة كوحدة نمطية NPM. في حالتي ، كانت VoxelJS ، مكتبة لبناء عوالم تشبه Minecraft في صفحة ويب. حاولت طلب المكتبة كـ WebJar ، لكن الجسر فشل ببساطة لأنه لا توجد حقول ترخيص في الواصف.
قد تواجه أيضًا هذه التجربة المحبطة لأسباب أخرى مع مكتبات أخرى. ببساطة ، يبدو أنه لا يمكنك الوصول إلى كل مكتبة في البرية مثل WebJar. يبدو أن المتطلبات التي يجب عليك استخدام WebJars للوصول إليها للوصول إلى مكتبات JavaScript محدودة للغاية.
أدخل NPM و Browserify
كما أشرت بالفعل ، فإن تنسيق الحزم القياسي لمعظم مكتبات JavaScript هو Node Package Manager ، أو NPM ، المضمن في أي إصدار من Node.js. باستخدام NPM ، يمكنك الوصول بسهولة إلى جميع مكتبات JavaScript المتاحة تقريبًا.
لاحظ أن NPM هو مدير حزم Node . Node هو تطبيق من جانب الخادم لمحرك V8 JavaScript ، ويقوم بتثبيت حزم ليتم استخدامها على جانب الخادم بواسطة Node.js. كما هو ، NPM عديم الفائدة للمتصفح. ومع ذلك ، فقد تم تمديد فائدة NPM لفترة من الوقت للعمل مع تطبيقات المستعرض ، وذلك بفضل أداة Browserify في كل مكان ، والتي يتم توزيعها بالطبع أيضًا كحزمة NPM.
Browserify هو ببساطة أداة تجميع للمتصفح. سيجمع وحدات NPM التي تنتج "حزمة" قابلة للاستخدام في تطبيق المستعرض. يعمل العديد من مطوري JavaScript بهذه الطريقة - فهم يديرون الحزم باستخدام NPM ، وبعد ذلك قم بالتصفح لاستخدامها في تطبيق الويب. ضع في اعتبارك أن هناك أدوات أخرى تعمل بنفس الطريقة ، مثل Webpack.
سد الفجوة من SBT إلى NPM
للأسباب التي وصفتها للتو ، ما أردته هو طريقة لتثبيت التبعيات من الويب باستخدام NPM ، واستدعاء Browserify لجمع التبعيات للمتصفح ، ثم استخدامها مع Scala.js. تبين أن المهمة أكثر تعقيدًا قليلاً مما كنت أتوقع ، لكنها لا تزال ممكنة. في الواقع ، لقد قمت بالمهمة وأنا أصفها هنا.
من أجل البساطة ، اخترت Browserify أيضًا لأنني وجدت أنه من الممكن تشغيله داخل SBT. لم أحاول استخدام Webpack ، على الرغم من أنني أعتقد أنه ممكن أيضًا. لحسن الحظ ، لم يكن علي أن أبدأ من الفراغ. يوجد عدد من القطع في مكانها بالفعل:
- SBT يدعم بالفعل NPM. يمكن للمكوِّن الإضافي
sbt-web
، الذي تم تطويره من أجل Play Framework ، تثبيت تبعيات NPM. - SBT يدعم تنفيذ JavaScript. يمكنك تنفيذ أدوات Node دون تثبيت Node نفسه ، وذلك بفضل المكون الإضافي
sbt-jsengine
. - يمكن لـ Scala.js استخدام حزمة مُنشأة. في Scala.js ، توجد وظيفة سلسلة لتضمينها في مكتبات JavaScript العشوائية في تطبيقك.
باستخدام هذه الميزات ، قمت بإنشاء مهمة SBT يمكنها تنزيل تبعيات NPM ثم استدعاء Browserify ، لإنتاج ملف bundle.js
. حاولت دمج الإجراء في سلسلة الترجمة ، ويمكنني تشغيل كل شيء تلقائيًا ، لكن الاضطرار إلى معالجة التجميع في كل تجميع هو ببساطة بطيء جدًا. أيضا ، أنت لا تغير التبعيات في كل وقت ؛ ومن ثم ، فمن المعقول أن تضطر إلى إنشاء حزمة يدويًا من حين لآخر عند تغيير التبعيات.
لذلك ، كان الحل هو إنشاء مشروع فرعي. يقوم هذا المشروع الفرعي بتنزيل مكتبات JavaScript وحزمها باستخدام NPM و Browserify. بعد ذلك ، أضفت أمر bundle
لأداء تجميع التبعيات. تتم إضافة الحزمة الناتجة إلى الموارد لاستخدامها في تطبيق Scala.js.
من المفترض أن تقوم بتنفيذ هذه "الحزمة" يدويًا كلما قمت بتغيير تبعيات JavaScript الخاصة بك. كما ذكرنا ، فهي ليست مؤتمتة في سلسلة التجميع.
كيفية استخدام Bundler
إذا كنت تريد استخدام المثال الخاص بي ، فقم بما يلي: أولاً ، قم بسحب المستودع باستخدام الأمر Git المعتاد.
git clone https://github.com/sciabarra/scalajs-browserify/
ثم انسخ مجلد bundle
في مشروع Scala.js الخاص بك. إنه مشروع فرعي للتجميع. للاتصال بالمشروع الرئيسي ، يجب إضافة الأسطر التالية في ملف build.sbt
الخاص بك:
val bundle = project.in(file("bundle")) jsDependencies += ProvidedJS / "bundle.js" addCommandAlias("bundle", "bundle/bundle")
أيضًا ، يجب عليك إضافة الأسطر التالية إلى ملف project/plugins.sbt
الخاص بك:
addSbtPlugin("com.typesafe.sbt" % "sbt-web" % "1.1.1") addSbtPlugin("com.typesafe.sbt" % "sbt-js-engine" % "1.1.3")
بمجرد الانتهاء ، لديك أمر جديد ، bundle
، يمكنك استخدامها لجمع تبعياتك. سيُنشئ ملف bundle.js
ضمن مجلد src/main/resources
.
كيف يتم تضمين الحزمة في تطبيق Scala.js الخاص بك؟
يقوم أمر bundle
الموصوف للتو بتجميع التبعيات باستخدام NPM ثم يقوم بإنشاء bundle.js
. عند تشغيل fastOptJS
أو fullOptJS
، فإن ScalaJS سيُنشئ myproject-jsdeps.js
، بما في ذلك جميع الموارد التي حددتها باعتبارها تبعية لـ JavaScript ، ومن ثم أيضًا bundle.js
. لتضمين التبعيات المجمعة في تطبيقك ، من المفترض أن تستخدم التضمينات التالية:
<script src="target/scala-2.11/myproject-jsdeps.js"></script> <script src="target/scala-2.11/myproject-fastopt.js"></script> <script src="target/scala-2.11/myproject-launcher.js"></script>
حزمتك متاحة الآن كجزء من myproject-jsdeps.js
. الحزمة جاهزة ، وقد انتهينا إلى حد ما من مهمتنا (استيراد التبعيات وتصديرها إلى المتصفح). الخطوة التالية هي استخدام مكتبات JavaScript ، وهي مشكلة مختلفة ، مشكلة ترميز Scala.js. للتأكد من اكتمالها ، سنناقش الآن كيفية استخدام الحزمة في Scala.js وإنشاء واجهات لاستخدام المكتبات التي قمنا باستيرادها.

استخدام مكتبة JavaScript عامة في تطبيق Scala.js الخاص بك
للتلخيص ، لقد رأينا للتو كيفية استخدام NPM و Browserify لإنشاء حزمة ، وتضمين هذه الحزمة في Scala.js. ولكن كيف يمكننا استخدام مكتبة JavaScript عامة؟
العملية الكاملة ، التي سنشرحها بالتفصيل في باقي المنشور ، هي:
- حدد مكتباتك من NPM وقم بتضمينها في
bundle/package.json
. - قم بتحميلها بـ
require
في ملف وحدة مكتبة ، فيbundle/lib.js
- اكتب واجهات Scala.js لتفسير كائن
Bundle
في Scala.js. - أخيرًا ، قم بترميز تطبيقك باستخدام المكتبات المكتوبة حديثًا.
إضافة التبعية
باستخدام NPM ، يجب عليك تضمين تبعياتك في ملف package.json
، وهو المعيار.
لذلك ، لنفترض أنك تريد استخدام مكتبتين مشهورتين مثل jQuery و Loadash. هذا المثال مخصص فقط لأغراض العرض ، نظرًا لوجود غلاف ممتاز لـ jQuery متاح كاعتماد لـ Scala.js مع وحدة نمطية مناسبة ، و Lodash عديم الفائدة في عالم Scala. ومع ذلك ، أعتقد أنه مثال جيد ، لكن خذها كمثال فقط.
لذلك ، انتقل إلى موقع الويب npmjs.com
وحدد موقع المكتبة التي تريد استخدامها وحدد إصدارًا أيضًا. لنفترض أنك اخترت الإصدار jquery-browserify
lodash
4.3.0. بعد ذلك ، قم بتحديث كتلة dependencies
الخاصة بـ packages.json
على النحو التالي:
"dependencies": { "browserify": "13.0.0", "jquery-browserify": "1.8.1", "lodash": "4.3.0" }
احتفظ دائمًا browserify
، حيث إنه ضروري لإنشاء الحزمة. لا يلزمك تضمينه في الحزمة ، بالرغم من ذلك.
لاحظ أنه إذا كان لديك NPM مثبتًا ، فيمكنك فقط الكتابة من دليل bundle
:
npm install --save jquery-browserify lodash
سيقوم أيضًا بتحديث package.json
. إذا لم يكن لديك NPM مثبتًا ، فلا تقلق. ستقوم SBT بتثبيت إصدار Java من Node.js و NPM ، وتنزيل JARs المطلوبة وتشغيلها. تتم إدارة كل هذا عند تشغيل أمر bundle
من SBT.
تصدير المكتبة
الآن نحن نعرف كيفية تنزيل الحزم. الخطوة التالية هي توجيه تعليمات لـ Browserify لتجميعها في حزمة وإتاحتها لبقية التطبيق.
require
هو عبارة عن مجمّع للمطالب ، يحاكي سلوك Node.js للمتصفح ، مما يعني أنك بحاجة إلى مكان ما require
استيراد مكتبتك. نظرًا لأننا نحتاج إلى تصدير هذه المكتبات إلى Scala.js ، فإن الحزمة تقوم أيضًا بإنشاء كائن JavaScript عالي المستوى يسمى Bundle
. لذا ، ما عليك فعله هو تحرير lib.js
، الذي يصدر كائن JavaScript ، ويتطلب جميع مكتباتك كحقول لهذا الكائن.
إذا أردنا التصدير إلى مكتبات Scala.js jQuery و Lodash ، فهذا يعني في الكود:
module.exports = { "jquery": require("jquery-browserify"), "lodash": require("lodash") }
الآن ، ما عليك سوى تنفيذ bundle
الأوامر وسيتم تنزيل المكتبة وتجميعها ووضعها في الحزمة ، لتكون جاهزة للاستخدام في تطبيق Scala.js الخاص بك.
الوصول إلى الحزمة
بعيد جدا:
- لقد قمت بتثبيت المشروع الفرعي للحزمة في مشروع Scala.js وتهيئته بشكل صحيح.
- لأية مكتبة تريدها ، أضفتها في
package.json
. - لقد طلبت منهم في
lib.js
- قمت بتنفيذ أمر
bundle
.
نتيجة لذلك ، لديك الآن Bundle
JavaScript ذات المستوى الأعلى من الحزمة ، توفر جميع نقاط الدخول للمكتبات ، المتاحة كحقول لهذا الكائن.
أنت الآن جاهز لاستخدامه مع Scala.js. في أبسط الحالات ، يمكنك القيام بشيء كهذا للوصول إلى المكتبات:
@js.native object Bundle extends js.Object { def jquery : js.Any = js.native def lodash: js.Any = js.native }
يتيح لك هذا الرمز الوصول إلى المكتبات من Scala.js. ومع ذلك ، فهذه ليست الطريقة التي يجب أن تفعلها مع Scala.js ، لأن المكتبات لا تزال غير مطبوعة. بدلاً من ذلك ، يجب أن تكتب واجهات أو أغلفة مكتوبة ، حتى تتمكن من استخدام مكتبات JavaScript غير المكتوبة في الأصل بطريقة متدرجة مكتوبة.
لا يمكنني إخبارك هنا بكيفية كتابة الواجهات لأنها تعتمد على مكتبة JavaScript المحددة التي تريد التفافها في Scala.js. سأعرض فقط مثالا ، لاستكمال المناقشة. يرجى مراجعة وثائق Scala.js الرسمية لمزيد من التفاصيل. يمكنك أيضًا الرجوع إلى قائمة الواجهات المتاحة وقراءة الكود المصدري للإلهام.
حتى الآن ، قمنا بتغطية عملية المكتبات التعسفية التي لم يتم تعيينها بعد. في بقية المقال ، أشير إلى مكتبة بواجهة متاحة بالفعل ، لذا فإن المناقشة ليست سوى مثال.
التفاف JavaScript API في Scala.js
عند استخدام require
في جافا سكريبت ، تحصل على كائن يمكن أن يكون العديد من الأشياء المختلفة. يمكن أن تكون وظيفة أو كائن. يمكن require
يكون مجرد سلسلة أو قيمة منطقية ، وفي هذه الحالة يتم استدعاء المطلب فقط من أجل الآثار الجانبية.
في المثال الذي أفعله ، jquery
هي وظيفة ، تعيد كائنًا يوفر طرقًا إضافية. الاستخدام المعتاد هو jquery(<selector>).<method>
. هذا تبسيط ، لأن jquery
يسمح أيضًا باستخدام $.<method>
، لكن في هذا المثال المبسط لن أقوم بتغطية كل هذه الحالات. ملاحظة بشكل عام ، بالنسبة لمكتبات JavaScript المعقدة ، لا يمكن تعيين جميع واجهات برمجة التطبيقات بسهولة لأنواع Scala الثابتة. قد تحتاج إلى اللجوء إلى js.Dynamic
لتوفير واجهة ديناميكية (غير نمطية) لكائن JavaScript.
لذلك ، لالتقاط حالة الاستخدام الأكثر شيوعًا ، حددت jquery
في كائن Bundle
:
def jquery : js.Function1[js.Any, Jquery] = js.native
ستعيد هذه الدالة كائن jQuery. تم تعريف مثيل سمة في حالتي بطريقة واحدة (تبسيط ، يمكنك إضافة الخاص بك):
@js.native trait Jquery extends js.Object { def text(arg: js.Any): Jquery = js.native }
بالنسبة لمكتبة Lodash ، قمنا بنمذجة المكتبة بأكملها ككائن JavaScript نظرًا لأنها مجموعة من الوظائف التي يمكنك الاتصال بها مباشرة:
def lodash: Lodash = js.native
حيث تكون سمة lodash
كما يلي (أيضًا تبسيط ، يمكنك إضافة طرقك هنا):
@js.native trait Lodash extends js.Object { def camelCase(arg: js.Any): String = js.native }
باستخدام هذه التعريفات ، يمكننا الآن كتابة رمز Scala أخيرًا باستخدام مكتبات jQuery و Lodash الأساسية ، وكلاهما تم تحميلهما من NPM ثم تصفحهما :
object Main extends JSApp { def main(): Unit = { import Bundle._ jquery("#title").text(lodash.camelCase("This is a test")) } }
يمكنك التحقق من المثال الكامل هنا.
خاتمة
أنا مطور Scala ، وكنت متحمسًا عندما اكتشفت Scala.js لأنه يمكنني استخدام نفس اللغة لكل من الخادم والعميل. نظرًا لأن Scala يشبه JavaScript أكثر من Java ، فإن Scala.js سهل جدًا وطبيعي في المتصفح. علاوة على ذلك ، يمكنك أيضًا استخدام الميزات القوية لـ Scala كمجموعة ثرية من المكتبات ووحدات الماكرو و IDEs القوية وأدوات البناء. المزايا الرئيسية الأخرى هي أنه يمكنك مشاركة التعليمات البرمجية بين الخادم والعميل. هناك الكثير من الحالات التي تكون فيها هذه الميزة مفيدة. إذا كنت تستخدم transpiler لجافا سكريبت مثل Coffeescript أو Babel أو Typescript ، فلن تلاحظ الكثير من الاختلافات عند استخدام Scala.js ، ولكن لا يزال هناك العديد من المزايا. السر هو الحصول على أفضل ما في كل العالم والتأكد من أنهم يعملون معًا بشكل جيد.