محمد بغات

الأعضاء
  • المساهمات

    17
  • تاريخ الانضمام

  • تاريخ آخر زيارة

السُّمعة بالموقع

4 Neutral

1 متابع

  1. السؤال الذي قد يتبادر إلى أذهان مُطوّري الجافا هو "ما الذي عليّ أن أتعلمه الآن؟". هناك مجموعة من اللغات التي تستحق الاعتبار، مثل Clojure, Rust أو Haskell. ولكن ماذا لو كنت تريد أن تتعلم شيئًا يساعدك على دفع الفواتير وهو فوق ذلك سهل الاستخدام؟ Kotlin قد يكون خيارك الأفضل، وسنحاول في هذه المقالة أن نشرح لماذا. ما هو Kotlin؟ لغة برمجة مُطوّرة من قبل JetBrains، والذين كانوا وراء فكرة الـ IDE بالإضافة إلى أشياء أخرى. بديل بسيط ومرن لجافا تتلاءم جيدًا مع أكواد جافا الموجودة تُترجم لجافا bytecode تعمل على JVM كما تُترجم أيضًا لجافا سكريبت إن كنت قد قرأت وثائقها فستلاحظ مجموعة من الأمور المهمة: تتيح لك القيام بالكثير من الأشياء بأكواد قليلة تحل الكثير من المشاكل الموجودة في جافا تساعدك على الاستمرار في استخدام النظام البيئي ecosystem المعتاد لجافا تتيح لك برمجة الواجهة الأمامية والخلفية بنفس اللغة 100٪ متوافقة مع الجافا أداؤها جيد مقارنةً بالبدائل (Clojure, Scala) لا تضيف إلّا طبقةً رقيقة من التّعقيد على جافا يبدو هذا جيدًا، أليس كذلك؟ فيما يلي سنرى بعض الأمثلة لمقارنتها بالجافا. عناصر القِيَم مقابل بيانات الأصناف Value objects vs data classes ما تراه هنا هو كائن جافا قديم ((POJO مع الأنماط المعتادة: public class HexagonValueObject { private final int x; private final int y; private final int z; public HexagonValueObject(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } public int getX() { return x; } public int getY() { return y; } public int getZ() { return z; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; HexagonValueObject hexagon = (HexagonValueObject) o; return getX() == hexagon.getX() && getY() == hexagon.getY() && getZ() == hexagon.getZ(); } @Override public int hashCode() { return Objects.hash(getX(), getY(), getZ()); } @Override public String toString() { return "HexagonValueObject{" + "x=" + x + ", y=" + y + ", z=" + z + '}'; } إنشاء عناصر القِيَم هو عملية مرهقة حتى باستخدام المكتبات مثل Lombok) Lombok يتطلب تثبيت ملحقة في بيئة العمل IDE الخاصة بك من أجل أن يعمل، وهو أمر قد لا يكون ممكنًا في جميع بيئات التطوير، يمكن تجاوز هذا المشكل بأدوات مثل Delombok، ولكنه اختراق أكثر منه حل للمشكلة)، على الأقل IDEA (أو Eclipse) يمنحك بعض المساعدة في توليد الكثير من تلك الوظائف، ولكنّ إضافة حقل ونسيان تعديل الوظيفة equals سيؤدي إلى مفاجآت سيئة. دعونا ننظر الآن إلى الكود المقابل في Kotlin: data class HexagonDataClass(val x: Int, val y: Int, val z: Int) مذهل! لقد اختصرنا الكثير من الكتابة بالمقارنة مع نسخة جافا. بيانات الأصناف في Kotlin تعطيك equals + hashCode و toString بالإضافة إلى المُحصّلات والمُعيِّنات (getters and setters). يمكنك أيضًا نسخها، مما يخلق كائنًا جديدًا مع إعادة كتابة بعض حقوله. حشو سلاسل النصوص String interpolation التعامل مع سلاسل النصوص في الجافا مرهق. ولكن يمكن تبسيطه باستخدام String.format ومع ذلك ستبقى قبيحة. public class JavaUser { private final String name; private final int age; public String toHumanReadableFormat() { return "JavaUser{" + "name='" + name + '\'' + ", age=" + age + '}'; } public String toHumanReadableFormatWithStringFormat() { return String.format("JavaUser{name='%s', age=%s}", name, age); } } Kotlin وجد حلّا لهذا، حيث أضاف مفهوم حشو السلاسل، مما بسّط استخدام المتغيرات في السلاسل الحرفية. ومكّن أيضًا استدعاء الوظائف منها! class KotlinUser(val name: String, val age: Int) { fun toHumanReadableFormat() = "JavaUser{name='$name', age=$age}" fun toHumanReadableFormatWithMethodCall() = "JavaUser{name='${name.capitalize()}', age=$age}" } دوال التمديد Extension functions كتابة المُلقّمات decorators في جافا يمكن أن يكون صعبًا، كما أنّها ليست مثالية. إذا كنت تريد أن تكتب مُلقّمًا، والذي يمكن استخدامه مع جميع الأصناف التي تقدّم List فلا يمكنك ببساطة استخدامه في الملقم خاصتك لأنّه سيحتاج منك أن تقدم الكثير من الوظائف الأخرى، لذلك ستكون مضطرًّا لتمديد AbstractList. public class ListPresenterDecorator<T> extends AbstractList<T> { private List<T> list; public ListPresenterDecorator(List<T> list) { this.list = list; } public String present() { return list.stream() .map(Object::toString) .collect(Collectors.joining(", ")); } @Override public T get(int index) { return list.get(index); } @Override public int size() { return list.size(); } } إذا كنت بحاجة إلى تلقيم شيء ما ولكنه لا يوفر أصنافًا أساسية base classes مفيدةً مثل AbstractList أو الصنف final، فأنت لا تستطيع ذلك ببساطة. لكن وظائف التمديد Extension methods يمكن أن تعطيك الحل! fun <T> List<T>.present() = this.joinToString(", ") تتصرف هذه الوظيفة كمُلقّم لجميع الأصناف Lists. لو قارنّا هذا ببديله في الجافا فإنّ هذ السطر الصغير أبسط بكثير، والأكثر من ذلك سوف يعمل أيضًا مع الأصناف final. حاول ألّا تسيء استخدامها وحسب. التحقق من العدمية Null safety التحقق من القيم المعدومة null ينطوي على الكثير من التعابير المنطقية والأنماط. مع ظهور الجافا، يمكنك أخيرًا التعامل مع هذه المشكلة بواسطة الصنف Optional، لكن ماذا لو كان مرجعOptional معدومًا null؟ ستحصل على NullPointerException، فلا زلنا بعد مرور 20 عام على ظهور جافا لا نستطيع تحديد العنصر الذي كان معدومًا. نأخذ المثال التالي: public class JavaUser { static class Address { String city; } private final String firstName; private final String lastName; private final List<Address> addresses; /** * If you want to make sure nothing is `null` * you have to check everything. */ public static String getFirstCity(JavaUser user) { if(user != null && user.addresses != null && !user.addresses.isEmpty()) { for(Address address : user.addresses) { if(address.city != null) { return address.city; } } } throw new IllegalArgumentException("This User has no cities!"); } } مع Kotlin، لديك العديد من الخيارات. إذا كان مشروعك متداخلًا مع مشاريع جافا يمكنك استخدام عامل التحقق من العدميّة (؟)null safety operator: data class KotlinUserWithNulls(val firstName: String?, // String? means that it is either a String object or a null val lastName: String?, val addresses: List<Address> = listOf()) { data class Address(val city: String?) companion object { fun fetchFirstCity(user: KotlinUserWithNulls?): String? { user?.addresses?.forEach { it.city?.let { return it } } return null } } } الكود الموجود بعد ؟ لن يُنفّذ إلا إن كان المعامل الأيسر operand غير معدوم not null. الدالة let تنشئ ارتباطًا محليًّا للكائن المُستدعى بحيث أنّ it سوف تشير إلى it.city. أما إن كان عملك مفصولًا عن الجافا فأقترح الابتعاد عن كل ما له علاقة بـ null data class KotlinUserWithoutNulls(val firstName: String, // this parameter can't be null val lastName: String, val addresses: List<Address> = listOf()) { data class Address(val city: String) companion object { fun fetchFirstCity(user: KotlinUserWithNulls) = user.addresses.first().city } } إذا لم يكن هناك أي استخدام لـ null (لا وجود لـ ؟) فكل شيء سيصبح أكثر بساطةً. استنباط النوع Type Inference Kotlin يدعم استنباط الأنواع، مما يعني أنه قادر على معرفة الأنواع من سياقها. أي مثل العامل الماسي diamond notation في الجافا <>! نأخذ المثال التالي: public class JavaUser { // ... private final String firstName; private final String lastName; private final List<Address> addresses; public Address getFirstAddress() { Address firstAddress = addresses.get(0); return firstAddress; } } هذا يبدو نفسه تقريبًا في Kotlin: data class KotlinUser(val firstName: String, val lastName: String, val addresses: List<Address> = listOf()) { data class Address(val city: String) /** * This is the same as in `JavaUser`. */ fun getFirstAddressNoInference(): Address { val firstAddress: Address = addresses.first() return firstAddress } } وذلك حتى تدع Kotlin تكتشف أنواع المتغيرات الخاصة بك: /** * Here the type of `firstAddress` is inferred from the context. */ fun getFirstAddressInferred(): Address { val firstAddress = addresses.first() return firstAddress } أو حتى الوظائف: /** * Here the return type is inferred. Note that * if a method consists of only one statement * you can omit the curly braces. */ fun getFirstAddress() = addresses.first() لا داعي للتحقق من الاستثناءات No checked exceptions ربما رأيت الكود التالي مليون مرة من قبل: public class JavaLineLoader { public List<String> loadLines(String path) { List<String> lines = new ArrayList<>(); try(BufferedReader br = new BufferedReader(new FileReader(path))) { String line; while((line = br.readLine()) != null) { lines.add(line); } } catch (IOException e) { e.printStackTrace(); } return lines; } } لأصحاب المدرسة القديمة في (IO in Java). لاحظوا التعليمة try في الكود أعلاه، الأمر نفسه سيُكتب هكذا في Kotlin: class KotlinLineLoader { fun loadLines(path: String) = File(path).readLines() } هناك بضعة أشياء ينبغي الانتباه إليها، أولًا: Kotlin ألغى التحقق من الاستثناءات. ثانيًا: Kotlin أضاف استخدام الكائنات من نوع Closeable أي: يمكنك أن تلاحظ أيضًا أنّ دالة التمديد (readLines) أضيفت إلى الصنف File. هذا النمط موجود بكثرة في Kotlin. إذا كنت قد استخدمت Guava أو Apache Commons أو شيء مماثل من قبل، فغالبًا سترى وظائف مشتركة تضاف منها إلى الصنف JDK كدالة تمديد. وهذا قد يكون مفيدًا لصحتك (أعصابك على الأقل). دعم لامبدا Lambda support دعونا نلقي نظرة على دعم lambda في الجافا: public class JavaFilterOperation { private List<String> items; @FunctionalInterface interface FilterOperation { Boolean filter(String element); } private List<String> filterBy(FilterOperation fn) { return items.stream() .filter(fn::filter) // applying the function .collect(Collectors.toList()); } public void doFilter() { filterBy((element) -> { return element.length() > 0; // calling the function with an actual lambda }); } } بما أنّه لا توجد قاعدة محددة لكتابة أنواع معاملات الوظائف method parameter types، فسيكون علينا إنشاء واجهة لها بأنفسنا. لاحظ أننا يمكن أن نستخدم Function<String, Boolean> هنا، ولكنها لن تعمل إلّا على الدوال ذات المعامل الواحد! هناك بعض الواجهات في JDK لحل هذه المشكلة، ولكن الكود سيكون مربكًا وغير واضح (مثلًا، ما دور BiFunction هنا؟)، Kotlin يحسّن هذا قليلًا: class KotlinFilterOperation { private val items = listOf<String>() fun filterBy(fn: (String) -> Boolean) = items.filter(fn) fun doFilter() { filterBy(String::isNotEmpty) // note the exension function `isNotEmpty` added to `String`! } } Kotlin يضيف قاعدةً نحويةً من أجل تمرير الدوال كمعاملات: (ParamType1, ...ParamTypeN) -> ReturnType. Kotlin يوفر مراجع للوظائف والحقول، كما يمكنك أيضًا التأشير إلى وظيفة من كائن ملموس! باستخدام المثال أعلاه يمكنني التأشير إلى الوظيفة filterBy من عيّنة instance ملموسة، هكذا: val reference = KotlinFilterOperation()::filterBy البرمجة الوظيفيّة Functional programming البرمجة الوظيفيّة أصبح لها ضجة في الوقت الحاضر. ومع جافا 8، قدمت Oracle مقاربتها لهذا الموضوع: الواجهة البرمجية Stream (Stream API). وهي تعمل كالتالي: public class JavaUser { // ... public static Set<String> fetchCitiesOfUsers( List<JavaUser> users) { return users.stream() .flatMap(user -> user.addresses.stream()) .map(JavaUser.Address::getCity) .collect(Collectors.toSet()); } } المقابل في Kotlin مشابه إلى حد ما، ولكن باختلافات دقيقة: fun fetchCitiesOfUsers(users: List<KotlinUser>) = users .flatMap(KotlinUser::addresses) .map(Address::city) .toSet() لا يوجد تحويل صريح للتسلسلات streams بما أنّ كل تجميعات Kotlin تدعمها. كنتيجة مباشرة لذلك، لن يكون عليك تمرير لامبدا لـ flatMap. تجميع النتيجة يحدث تلقائيًّا (لا حاجة لاستدعاء الوظيفة *Collectors.to). واستخدامنا لـ toSet هنا سببه الوحيد أننا نريد إرجاع عنصر من نوع Set. وإلا فيمكن حذف.toSet() توافقية جافا وKotlin يمكن أن يكون هذا معقدًا لمعظم النّاس، لكن JetBrains تعاملت مع هذا الأمر بالشكل الصحيح: public class KotlinInterop { public void helloJava() { System.out.println("Hello from Java!"); } public void helloKotlin() { JavaInterop.createInstance().helloKotlin(); } } class JavaInterop { fun helloJava() { KotlinInterop().helloJava() } fun helloKotlin() { println("Hello from Kotlin!") } companion object { @JvmStatic fun createInstance() = JavaInterop() } } التداخل هنا سلس وسهل. جافا وKotlin يمكن أن يتعايشا معًا في نفس المشروع، كما يقدم Kotlin مجموعة من التعبيرات (مثل @JvmStatic) لتسهيل استدعاء كود Kotlin من الجافا دون أية مشاكل. قد تكون قد لاحظت من هذه الأمثلة أن Kotlin يستعير الأفكار الجيدة من جافا ويُحسّنها ويحاول أن يختصر عليك الوقت والعمل إلى أدنى حد ممكن. الأنباء الأخيرة حول اعتزام جوجل جعل Kotlin واحدةً من اللغات المعتمدة علىAndroid تدعم هذا التوجه أيضًا. أشياء لتخبر بها مديرك: إذا كنت ترغب في إعطاء Kotlin فرصة فإليك بعض النصائح التي ستساعدك عند التفاوض مع مديرك أو زملائك في الفريق: Kotlin خرج من عالم الصناعة، وليس من العالم الأكاديمي. فهو يحل المشاكل التي يواجها المبرمجون اليوم. حر ومفتوح المصدر يأتي ومعه أداة مفيدة لتحويل جافا إلى Kotlin يمكنك مزج Kotlin وجافا دون جهد يُذكر يمكنك استخدام جميع أدوات وأُطُر عمل جافا الموجودة Kotlin مدعوم من قبل أفضل بيئة عمل في السوق (مع نسخة مجانية) من السهل قراءته، فحتى المبرمجون غير المتخصصون في Kotlin يمكنهم مراجعة الأكواد المكتوبة به لا تحتاج إلى رهن مشروعك بـ Kotlin: يمكنك البدء في المرحلة الأولى بكتابة الاختبارات فيه. من غير المرجح أن تتخلى JetBrains عن Kotlin لأنه يُقوّي مبيعاتها Kotlin لديه مجتمع حيوي، كما يمكنك بسهولة أن تساهم في Kotlin وتقترح ميزات جديدة باستخدام KEEP إذن، هل يستحق Kotlin الضجة المثارة حوله؟ الحكم لك. ترجمة -وبتصرّف- للمقال Kotlin is the new Java لكاتبه Adam Arold
  2. شكرا، مقال مفيد
  3. مدخل إلى برمجة إضافات ووردبريس

    شكرا على المقال.
  4. ملاحظة: هذا الدليل يفترض أنك تستخدم Babel، كما يتطلّب استخدام babel-preset-airbnb أو ما يماثله. ويفترض أيضًا أنّك قمت بتثبيت babel-preset-airbnb في تطبيقك، عبر airbnb-browser-shims أو ما يماثله. الأنواع الأنواع الأساسية عندما تتعامل مع أحد الأنواع الأساسية فأنت تعمل مباشرةً على قيمته. String number boolean null undefined symbol const foo = 1; let bar = foo; bar = 9; console.log(foo, bar); // => 1, 9 الرموز Symbols لا يمكن أن تُدرج (polyfilled) بدقة، لذلك لا ينبغي استخدامها عند استهداف المتصفحات/البيئات التي لا تدعمها تلقائيًّا. الأنواع المركبة (Complex) عند التعامل مع نوع مركّب فأنت تعمل على مرجعٍ لقيمته. object Array function const foo = [1, 2]; const bar = foo; bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9 المراجع References استخدم const لجميع مراجعك. وتجنب استخدام var. لماذا؟ لأنّ هذا سيضمن لك ألّا تعيد تعيين مراجعك، والذي يمكن أن يؤدي إلى أخطاء، ويُصعّب فهم الكود البرمجي. // bad var a = 1; var b = 2; // good const a = 1; const b = 2; إن كنت مضطرًّا لإعادة تعيين المراجع، استخدم let بدلاً من var. لماذا؟ لأن مدى let محدود في الكتلة البرمجية (block-scoped) وليس محدودًا داخل الدالة (function-scoped) كما هو الحال مع var. // bad var count = 1; if (true) { count += 1; } // good, use the let. let count = 1; if (true) { count += 1; } تذكر أن مدى كل من let و constمحدود في الكتلة (block-scoped). // const and let only exist in the blocks they are defined in. { let a = 1; const b = 1; } console.log(a); // ReferenceError console.log(b); // ReferenceError الكائنات Objects استخدم الأسلوب الحرفي لإنشاء الكائن. // bad const item = new Object(); // good const item = {}; استخدم أسماءً محسوبةً للخصائص computed property names عند إنشاء كائنات ذات أسماء خصائص ديناميكية. لماذا؟ لأن هذا سيسمح لك بتعريف جميع خصائص الكائن في مكان واحد. function getKey(k) { return `a key named ${k}`; } // bad const obj = { id: 5, name: 'San Francisco', }; obj[getKey('enabled')] = true; // good const obj = { id: 5, name: 'San Francisco', [getKey('enabled')]: true, }; استخدم أسلوب التعريف المختصر لوظائف الكائن. // bad const atom = { value: 1, addValue: function (value) { return atom.value + value; }, }; // good const atom = { value: 1, addValue(value) { return atom.value + value; }, }; استخدم التعريف المختصر لقيمة الخاصية. لماذا؟ لأنه أقصر وأوضح. const lukeSkywalker = 'Luke Skywalker'; // bad const obj = { lukeSkywalker: lukeSkywalker, }; // good const obj = { lukeSkywalker, }; اجمع الخصائص المُختصرة في بداية التصريح بالكائن object declaration. لماذا؟ لأنه من السهل هكذا معرفة أي الخصائص تستخدم الاختصار. const anakinSkywalker = 'Anakin Skywalker'; const lukeSkywalker = 'Luke Skywalker'; // bad const obj = { episodeOne: 1, twoJediWalkIntoACantina: 2, lukeSkywalker, episodeThree: 3, mayTheFourth: 4, anakinSkywalker, }; // good const obj = { lukeSkywalker, anakinSkywalker, episodeOne: 1, twoJediWalkIntoACantina: 2, episodeThree: 3, mayTheFourth: 4, }; لا تضع بين علامات التنصيص إلّا الخصائص التي لها أسماء غير صالحة. لماذا؟ بشكل عام، لأنّه أسهل للقراءة ويُحسّن وضوح الكود، كما أنّه يسهُل استخدامه من قبل محركات الجافا سكريبت. // bad const bad = { 'foo': 3, 'bar': 4, 'data-blah': 5, }; // good const good = { foo: 3, bar: 4, 'data-blah': 5, }; لا تستدعي وظائف Object.prototype مباشرة، مثل hasOwnProperty، propertyIsEnumerable، و isPrototypeOf. لماذا؟ تلك الوظائف قد تُظلّل shadowed من قبل خصائص الكائن المَعني – مثلًا {hasOwnProperty: false} –أو قد يكون الكائن معدومًا (Object.create(null)). // bad console.log(object.hasOwnProperty(key)); // good console.log(Object.prototype.hasOwnProperty.call(object, key)); // best const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope. /* or */ import has from 'has'; // https://www.npmjs.com/package/has // ... console.log(has.call(object, key)); يفضل استخدام عامل التناقل (spread operator) على الكائن بدلًا من Object.assign إن كنت تريد القيام بالنسخ السطحي (shallow-copy) للكائنات. أو يمكنك استخدام العامل الساكن (rest operator) للحصول على كائن جديد مع حذف خصائص معينة. // very bad const original = { a: 1, b: 2 }; const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ delete copy.a; // so does this // bad const original = { a: 1, b: 2 }; const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 } // good const original = { a: 1, b: 2 }; const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 } const { a, ...noA } = copy; // noA => { b: 2, c: 3 } الصفوف Arrays استخدم التعبير الحرفي لإنشاء الصف. // bad const items = new Array(); // good const items = []; استخدم Array#push بدلًا من الإحالة المباشرة لإضافة عناصر إلى الصف. const someStack = []; // bad someStack[someStack.length] = 'abracadabra'; // good someStack.push('abracadabra'); استخدم تناقل الصفوف (array spreads) لنسخ الصفوف. // bad const len = items.length; const itemsCopy = []; let i; for (i = 0; i < len; i += 1) { itemsCopy[i] = items[i]; } // good const itemsCopy = [...items]; لتحويل كائن شبيه بالصفوف إلى صف، استخدم التناقل بدلًا من Array.from. const foo = document.querySelectorAll('.foo'); // good const nodes = Array.from(foo); // best const nodes = [...foo]; استخدم Array.from بدلًا من التناقل لأجل تطبيق إجراء على المكرّرات mapping over iterables، وذلك لتجنب خلق صف مؤقت. // bad const baz = [...foo].map(bar); // good const baz = Array.from(foo, bar); استخدم العبارةreturn في استدعاءات وظائف الصف array method callbacks. لا ضير في حذفreturn إن كان متن الدالة function body يتكون من عبارة واحدة من دون آثار جانبية. // good [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); // good [1, 2, 3].map(x => x + 1); // bad - no returned value means `memo` becomes undefined after the first iteration [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => { const flatten = memo.concat(item); memo[index] = flatten; }); // good [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => { const flatten = memo.concat(item); memo[index] = flatten; return flatten; }); // bad inbox.filter((msg) => { const { subject, author } = msg; if (subject === 'Mockingbird') { return author === 'Harper Lee'; } else { return false; } }); // good inbox.filter((msg) => { const { subject, author } = msg; if (subject === 'Mockingbird') { return author === 'Harper Lee'; } return false; }); استخدم سطرًا جديدًا بعد معقوفة الفتح وقبل معقوفة إغلاق الصف إذا كان الصف متعدد الأسطر. // bad const arr = [ [0, 1], [2, 3], [4, 5], ]; const objectInArray = [{ id: 1, }, { id: 2, }]; const numberInArray = [ 1, 2, ]; // good const arr = [[0, 1], [2, 3], [4, 5]]; const objectInArray = [ { id: 1, }, { id: 2, }, ]; const numberInArray = [ 1, 2, ]; التفكيك Destructuring استخدم تفكيك الكائن عند التعامل مع عدة خصائص للكائن. لماذا؟ التفكيك يُعفيك من الحاجة إلى إنشاء مراجع مؤقتة لتلك الخصائص. // bad function getFullName(user) { const firstName = user.firstName; const lastName = user.lastName; return `${firstName} ${lastName}`; } // good function getFullName(user) { const { firstName, lastName } = user; return `${firstName} ${lastName}`; } // best function getFullName({ firstName, lastName }) { return `${firstName} ${lastName}`; } استخدم تفكيك الصفوف. const arr = [1, 2, 3, 4]; // bad const first = arr[0]; const second = arr[1]; // good const [first, second] = arr; استخدم تفكيك الكائن لأجل إرجاع أكثر من قيمة واحدة multiple return values، ولا تستخدم تفكيك الصفوف. لماذا؟ يمكنك إضافة خاصيات جديدة مع الوقت وتغيير الترتيب دون مشاكل. // bad function processInput(input) { // then a miracle occurs return [left, right, top, bottom]; } // the caller needs to think about the order of return data const [left, __, top] = processInput(input); // good function processInput(input) { // then a miracle occurs return { left, right, top, bottom }; } // the caller selects only the data they need const { left, top } = processInput(input); النصوص Strings استخدم علامات التنصيص المفردة ’ لأجل النصوص. // bad const name = "Capt. Janeway"; // bad - template literals should contain interpolation or newlines const name = `Capt. Janeway`; // good const name = 'Capt. Janeway'; النصوص التي يتجاوز طولها 100 حرف لا ينبغي أن تُكتب على أسطر متعددة باستخدام ضمّ النصوص concatenation. لماذا؟ النصوص التي فيها أخطاء تكون مزعجةً وتجعل الكود أقل قابلية للبحث. // bad const errorMessage = 'This is a super long error that was thrown because \ of Batman. When you stop to think about how Batman had anything to do \ with this, you would get nowhere \ fast.'; // bad const errorMessage = 'This is a super long error that was thrown because ' + 'of Batman. When you stop to think about how Batman had anything to do ' + 'with this, you would get nowhere fast.'; // good const errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.'; عند بناء النصوص برمجيا، حاول استخدام قوالب النصوص بدلًا من الضمّ concatenation. لماذا؟ قوالب النصوص تجعل الكود أكثر مقروئية وإيجازًا مع أسطر جديدة مناسبة وميزات حشو النصوصstring interpolation features. // bad function sayHi(name) { return 'How are you, ' + name + '?'; } // bad function sayHi(name) { return ['How are you, ', name, '?'].join(); } // bad function sayHi(name) { return `How are you, ${ name }?`; } // good function sayHi(name) { return `How are you, ${name}?`; } لا تستخدم أبدًا ()eval على النصوص، لأنها تفتح الكثير من نقاط الضعف. تجنب تحريف الأحرف (escape characters) في النصوص قدر الإمكان. لماذا؟ الشرطة المائلة Backslashes تضر بالمقروئية، وبالتالي يجب ألًا تُستخدم إلا عند الضرورة. // bad const foo = '\'this\' \i\s \"quoted\"'; // good const foo = '\'this\' is "quoted"'; const foo = `my name is '${name}'`; الدوال استخدم عبارات الدالة المُسماة named function expression بدلًا من تصريحات الدوال function declarations لماذا؟ تصريحات الدوال تُرفَّع hoisted إلى الأعلى، ما يعني أنه سيكون من السهل الإحالة إلى الدالة قبل أن يتم تعريفها في الملف. وهذا يضر المقروئية والصيانة. إذا وجدت أن تعريف دالة ما كبير أو معقد بشكل مربك، فربما حان الوقت لوضعه في وحدة خاصة به! لا تنسى أن تُسمي التعبير صراحة، بغض النظر عما إذا كان الاسم مستنتجًا من المتغير الحاوي inferred from the containing variable (كما هو الحال غالبًا في المتصفحات الحديثة أو عند استخدام متراجمات Babel). هذا سيلغي أي افتراضات حول مكدّس الخطأ Error’s call stack // bad function foo() { // ... } // bad const foo = function () { // ... }; // good // lexical name distinguished from the variable-referenced invocation(s) const short = function longUniqueMoreDescriptiveLexicalFoo() { // ... }; ضع الدوال المستدعاة فورًا بين قوسين. لماذا؟ الدوال المستدعاة فورًا هي عبارةٌ واحدة – لذلك وضعها هي وعبارة استدعائها بين قوسين يجعل الكود واضحًا. وإن كان من المستبعد جدّا أن تحتاج الدوال المستدعاة فورًا IIFE)). // immediately-invoked function expression (IIFE) (function () { console.log('Welcome to the Internet. Please follow me.'); }()); لا تصرّح أبدًا بالدوال في كتلة غير خاصة بالدوال non-function block (مثل if, while …). قم بإحالة الدالة إلى متغير بدلًا من ذلك. المتصفحات ستسمح لك بفعل ذلك، ولكنّها ستفسرها بطرق مختلفة، وهو أمر لا يحبه المبرمجون. ملاحظة: ECMA-262 يٌعرّف الكتلة block كقائمة من التعليمات. وتعريف الدالة ليس تعليمةً. // bad if (currentUser) { function test() { console.log('Nope.'); } } // good let test; if (currentUser) { test = () => { console.log('Yup.'); }; } لا تسمّي معاملًا بالاسم arguments. لأنه سيأخذ الأسبقية على الكائن arguments الذي يُحدد تلقائيّا في نطاق كل الدوال. // bad function foo(name, options, arguments) { // ... } // good function foo(name, options, args) { // ... } لا تستخدم أبدًا arguments، واستخدم الأسلوب الساكن rest syntax بدلًا من ذلك. لماذا؟ واضحة في تحديد المعاملات arguments التي تريد سحبها. بالإضافة إلى ذلك، المعاملات الساكنة هي في الأصل صف، وليست شبيهة بالصفوف مثل arguments. // bad function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // good function concatenateAll(...args) { return args.join(''); } استخدم أسلوب المعاملات الافتراضية بدلًا من المعاملات المتحوّلة mutating function arguments. // really bad function handleThings(opts) { // No! We shouldn’t mutate function arguments. // Double bad: if opts is falsy it'll be set to an object which may // be what you want but it can introduce subtle bugs. opts = opts || {}; // ... } // still bad function handleThings(opts) { if (opts === void 0) { opts = {}; } // ... } // good function handleThings(opts = {}) { // ... } تجنب الآثار الجانبية في المعاملات الافتراضية. لماذا؟ لأنّها مربكة. var b = 1; // bad function count(a = b++) { console.log(a); } count(); // 1 count(); // 2 count(3); // 3 count(); // 3 ضع دائمًا المعاملات الافتراضية في الأخير. // bad function handleThings(opts = {}, name) { // ... } // good function handleThings(name, opts = {}) { // ... } لا تستخدم أبدًا منشئ الدوال Function لإنشاء دالة جديدة. لماذا؟ إنشاء دالة بهذه الطريقة يتضمّن تنفيذ نص كما تفعل ()eval، وهو ما يفتح نقاط ضعف. // bad var add = new Function('a', 'b', 'return a + b'); // still bad var subtract = Function('a', 'b', 'return a - b'); ضع مسافة في توقيع الدالة. لماذا؟ الاتساق أمر جيد، كما لن تكون مضطرًا لإضافة أو إزالة مسافة عند إضافة أو إزالة اسم. // bad const f = function(){}; const g = function (){}; const h = function() {}; // good const x = function () {}; const y = function a() {}; لا تحوًل mutate المعاملات أبدًا. لماذا؟ يمكن للتعامل مع الكائنات التي مُرُرت كمعاملات parameters أن يتسبب في آثار جانبية غير مرغوب فيها في المستدعي الأصلي. // bad function f1(obj) { obj.key = 1; } // good function f2(obj) { const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1; } لا تُعد تعيين المعاملات. لماذا؟ إعادة تعيين المعاملات يمكن أن يؤدي إلى سلوك غير متوقع، خصوصًا عند التعامل مع الكائن arguments. كما يمكن أن يسبب مشاكل في الأداء، خصوصا في V8. // bad function f1(a) { a = 1; // ... } function f2(a) { if (!a) { a = 1; } // ... } // good function f3(a) { const b = a || 1; // ... } function f4(a = 1) { // ... } من الأفضل استخدام العملية التناقلية لاستدعاء الدوال المرنةvariadic functions . لماذا؟ لأنها أوضح، فلست مضطرًّا لتجهيز السياق، كما لا يمكنك أن تجمع بسهولة new مع apply. // bad const x = [1, 2, 3, 4, 5]; console.log.apply(console, x); // good const x = [1, 2, 3, 4, 5]; console.log(...x); // bad new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5])); // good new Date(...[2016, 8, 5]); الدوال التي لها توقيعات أو استدعاءات متعددة الأسطر، ينبغي أن تكون مسافاتها البادئة تمامًا مثل كل القوائم متعددة الأسطر الأخرى في هذا الدليل: حيث كل عنصر في سطر، مع فاصلة زائدة بعد العنصر الأخير. // bad function foo(bar, baz, quux) { // ... } // good function foo( bar, baz, quux, ) { // ... } // bad console.log(foo, bar, baz); // good console.log( foo, bar, baz, ); الدوال السهميّة Arrow Functions عندما تكون مضطرًّا لاستخدام دالة مجهولة، استخدم أسلوب الدالة السهمية. لماذا؟ لأنها تخلق نسخة من الدالة والتي يمكن تنفيذها في سياق this، وهو عادةً ما ترغب فيه، كما أنها أكثر إيجازًا. متى لا تستخدمها؟ إذا كانت لديك دالة معقدة إلى حد ما، فقد ترغب في تعريف الدالة بشكل مستقل، ثم استخدامها في العبارة. // bad [1, 2, 3].map(function (x) { const y = x + 1; return x * y; }); // good [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); إن كان متن الدالة function body يتكون من تعليمة واحدة تُرجِع تعبيرًا دون آثار جانبية، فاحذف اللّامّتين واستخدم return الضمنية. خلافًا لذلك، أبق على الأقواس واستخدم العبارة return. لماذا؟ تٌسهل قراءة الكود عند استخدام دوال متعددة معًا. // bad [1, 2, 3].map(number => { const nextNumber = number + 1; `A string containing the ${nextNumber}.`; }); // good [1, 2, 3].map(number => `A string containing the ${number}.`); // good [1, 2, 3].map((number) => { const nextNumber = number + 1; return `A string containing the ${nextNumber}.`; }); // good [1, 2, 3].map((number, index) => ({ [index]: number, })); // No implicit return with side effects function foo(callback) { const val = callback(); if (val === true) { // Do something if callback returns true } } let bool = false; // bad foo(() => bool = true); // good foo(() => { bool = true; }); في حالة امتد التعبير عبر عدة أسطر، ضعه بين قوسين. لماذا؟ لتوضيح أين تبدأ وأين تنتهي الدالة. // bad ['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod, ) ); // good ['get', 'post', 'put'].map(httpMethod => ( Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod, ) )); إن كان للدالة معامل واحد فقط ولا تستخدم اللامّتين، فقم بحذف الأقواس. خلافًا ذلك، دائمًا ضع أقواسًا حول المعاملات من أجل مزيد من الوضوح والاتساق. ملاحظة: من المقبول أيضًا أن تستخدم الأقواس دائمًا، وفي هذه الحالة استخدم الخيارalways"". لماذا؟ لأجل وضوح أكبر. // bad [1, 2, 3].map((x) => x * x); // good [1, 2, 3].map(x => x * x); // good [1, 2, 3].map(number => ( `A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!` )); // bad [1, 2, 3].map(x => { const y = x + 1; return x * y; }); // good [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); تجنب الخلط بين عبارات الدوال السهمية(=>) وبين عوامل المقارنة (<= , >=). // bad const itemHeight = item => item.height > 256 ? item.largeSize : item.smallSize; // bad const itemHeight = (item) => item.height > 256 ? item.largeSize : item.smallSize; // good const itemHeight = item => (item.height > 256 ? item.largeSize : item.smallSize); // good const itemHeight = (item) => { const { height, largeSize, smallSize } = item; return height > 256 ? largeSize : smallSize; }; الأصناف والمُنشِئات Classes & Constructors استخدم دائمًا class. وتجنب التعامل مع prototype مباشرةً. لماذا؟ العبارة class أكثر إيجازا ووضوحًا. // bad function Queue(contents = []) { this.queue = [...contents]; } Queue.prototype.pop = function () { const value = this.queue[0]; this.queue.splice(0, 1); return value; }; // good class Queue { constructor(contents = []) { this.queue = [...contents]; } pop() { const value = this.queue[0]; this.queue.splice(0, 1); return value; } } استخدم extends لأجل الوراثة. لماذا؟ لأنها وسيلة مدمجة لوراثة نموذج الوظائف extends دون التسبب بمشاكل مع instanceof. // bad const inherits = require('inherits'); function PeekableQueue(contents) { Queue.apply(this, contents); } inherits(PeekableQueue, Queue); PeekableQueue.prototype.peek = function () { return this.queue[0]; }; // good class PeekableQueue extends Queue { peek() { return this.queue[0]; } } يمكن للوظائف أن تُرجع this للمساعدة بخصوص تسلسل الوظائف method chaining. // bad Jedi.prototype.jump = function () { this.jumping = true; return true; }; Jedi.prototype.setHeight = function (height) { this.height = height; }; const luke = new Jedi(); luke.jump(); // => true luke.setHeight(20); // => undefined // good class Jedi { jump() { this.jumping = true; return this; } setHeight(height) { this.height = height; return this; } } const luke = new Jedi(); luke.jump() .setHeight(20); لا بأس في كتابة وظائف ()toString مخصصة، فقط تأكد من أنها تعمل بسلاسة ولا تتسبب في أي آثار جانبية. class Jedi { constructor(options = {}) { this.name = options.name || 'no name'; } getName() { return this.name; } toString() { return `Jedi - ${this.getName()}`; } } الأصناف لديها منشئ افتراضي إذا لم يتم تحديد واحد سلفًا. لذلك فالمنشئات الفارغة أو التي تنوب عن الصنف الأب وحسب ليست ضرورية. // bad class Jedi { constructor() {} getName() { return this.name; } } // bad class Rey extends Jedi { constructor(...args) { super(...args); } } // good class Rey extends Jedi { constructor(...args) { super(...args); this.name = 'Rey'; } } تجنب تكرار عناصر الصنف. لماذا؟ في حال التصريح المكرر لعنصر من عناصر الصنف، فالقيمة الأخيرة فقط هي التي ستُعتبر – وجود التكرارات يعني بالضرورة أن هناك مشكلة في الكود. // bad class Foo { bar() { return 1; } bar() { return 2; } } // good class Foo { bar() { return 1; } } // good class Foo { bar() { return 2; } } الوحدات Modules دائمًا استخدم (import/export) لأجل الوحدات بدلًا من الطرق الأخرى غير القياسية. يمكنك الترجمة transpile دائمًا نحو نظام الوحدات المفضل لديك. لماذا؟ الوحدات هي المستقبل، دعونا نُدشن المستقبل منذ الآن. // bad const AirbnbStyleGuide = require('./AirbnbStyleGuide'); module.exports = AirbnbStyleGuide.es6; // ok import AirbnbStyleGuide from './AirbnbStyleGuide'; export default AirbnbStyleGuide.es6; // best import { es6 } from './AirbnbStyleGuide'; export default es6; لا تستخدم الاستيراد الشامل wildcard imports. لماذا؟ لتتأكد من أنّ لديك تصديرًا افتراضيّا واحدًا. // bad import * as AirbnbStyleGuide from './AirbnbStyleGuide'; // good import AirbnbStyleGuide from './AirbnbStyleGuide'; لا تُصدّر مباشرةً من الاستيراد. لماذا؟ على الرغم من أن الكتابة في سطر واحد تكون أوجز، وجود طريقة واضحة واحدة للاستيراد وأخرى للتصدير يجعل الأمور متسقةً. // bad // filename es6.js export { es6 as default } from './AirbnbStyleGuide'; // good // filename es6.js import { es6 } from './AirbnbStyleGuide'; export default es6; استورد من المسار path في مكان واحد فقط. لماذا؟ وجود عدة أسطر تستورد من نفس المسار يمكن أن يجعل الكود أقل قابلية للصيانة. // bad import foo from 'foo'; // … some other imports … // import { named1, named2 } from 'foo'; // good import foo, { named1, named2 } from 'foo'; // good import foo, { named1, named2, } from 'foo'; لا تُصدر الارتباطات المتحوّلة mutable bindings. لماذا؟ العناصر المتحولة يُفضل تجنبها بشكل عام، ولكن على وجه الخصوص عند تصدير الارتباطات المتحولة. في حين أن هذا الأسلوب قد يكون ضروريّا في بعض الحالات الخاصة، فبشكل عام، يجب ألّا تُصدرَ إلا المراجع الثابتة. // bad let foo = 3; export { foo }; // good const foo = 3; export { foo }; في الوحدات التي فيها تصدير واحد فقط، يفضل استخدام التصدير الافتراضي بدلًا من التصدير المسمى named export. لماذا؟ لتشجيع استخدام الملفات التي لا تصدر إلًا شيءً واحدًا فقط، لأن ذلك أفضل وأسهل للقراءة والصيانة. // bad export function foo() {} // good export default function foo() {} ضع كل العبارات import فوق عبارات الاستيراد الأخرى. لماذا؟ لأن import يتم ترفيعهاhoisted ، فوضعها كلها في الجزء العلوي يمنع أي سلوك غير متوقع. // bad import foo from 'foo'; foo.init(); import bar from 'bar'; // good import foo from 'foo'; import bar from 'bar'; foo.init(); الاستيراد المتعدد يجب أن توضع قبله مسافة بادئة مثله مثل الصفوف متعددة الأسطر والتصريح الحرفي للكائنات object literals. لماذا؟ اللامّتان تتبعان نفس قواعد المسافة البادئة كما هو الحال في كل كتل اللامّات في هذا الدليل، وكذلك مثل الفواصل الزائدة trailing commas. // bad import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path'; // good import { longNameA, longNameB, longNameC, longNameD, longNameE, } from 'path'; تجنب تعابير مُحمّلات الحُزم Webpack loader في عبارات import. لماذا؟ بما أن مُحمّلات الحزم تستخدم معدات الوحدات module bundler. فمن الأفضل استخدام عبارة المُحمّل في webpack.config.js. // bad import fooSass from 'css!sass!foo.scss'; import barCss from 'style!css!bar.css'; // good import fooSass from 'foo.scss'; import barCss from 'bar.css'; المُكرّرات والمولّدات لا تستخدم المكررات. من الأفضل استخدام دوال الدرجات العليا higher-order functions لجافا سكريبت بدلًا من الحلقات مثلfor-in أو for-of. لماذا؟ هذا يتماشى مع قاعدة التحول immutable rule الخاصة بنا. التعامل مع الدوال التي تُرجع قيمًا أسهل للفهم مقارنة بالدوال ذات الآثار الجانبية. استخدم ()map / every() / filter() / find() / findIndex() / reduce() / some / للتمرير iterate على الصفوف، و ()Object.keys / Object.values() / Object.entries لإنتاج صف حتى تتمكن من التمرير على عناصره. const numbers = [1, 2, 3, 4, 5]; // bad let sum = 0; for (let num of numbers) { sum += num; } sum === 15; // good let sum = 0; numbers.forEach((num) => { sum += num; }); sum === 15; // best (use the functional force) const sum = numbers.reduce((total, num) => total + num, 0); sum === 15; // bad const increasedByOne = []; for (let i = 0; i < numbers.length; i++) { increasedByOne.push(numbers[i] + 1); } // good const increasedByOne = []; numbers.forEach((num) => { increasedByOne.push(num + 1); }); // best (keeping it functional) const increasedByOne = numbers.map(num => num + 1); لا تستخدم المولدات في الوقت الراهن. لماذا؟ لأنها لا تُترجم transpile جيدًا في ES5. إذا كان عليك استخدام المُولدات، أو إذا أردت تجاهل نصيحتنا، تأكد من أن تواقيع دوالها متباعدة بشكل صحيح. لماذا؟ function و * هما كلمتان مفتاحيتان من نفس المفهوم conceptual keyword – * ليست تعديلًا لـ function و function* مختلفة عن function. // bad function * foo() { // ... } // bad const bar = function * () { // ... }; // bad const baz = function *() { // ... }; // bad const quux = function*() { // ... }; // bad function*foo() { // ... } // bad function *foo() { // ... } // very bad function * foo() { // ... } // very bad const wat = function * () { // ... }; // good function* foo() { // ... } // good const foo = function* () { // ... }; الخصائص استخدم أسلوب النقطة dot notation عند الدخول إلى الخصائص. const luke = { jedi: true, age: 28, }; // bad const isJedi = luke['jedi']; // good const isJedi = luke.jedi; استخدام المعقوفتين [] عند الدخول إلى الخصائص بواسطة متغير. const luke = { jedi: true, age: 28, }; function getProp(prop) { return luke[prop]; } const isJedi = getProp('jedi'); استخدام العامل الأسي ** عند حساب الأسّ. // bad const binary = Math.pow(2, 10); // good const binary = 2 ** 10; المتغيرات استخدم دومًا const أو let لتعريف المتغيرات. عدم القيام بذلك سيؤدي إلى إنشاء متغيرات كلية global variables. ونحن نريد تجنب تلويث مجال الأسماء ((namespace العام. // bad superPower = new SuperPower(); // good const superPower = new SuperPower(); استخدم const أو let لكل متغير على حدة. لماذا؟ من الأسهل إضافة متغيرات جديدة بهذه الطريقة، ولن تكون مضطرا لتحريك ; أو,. وسيسهل أيضًا استخدام المنقح debugger على كل واحد منهم على حدة، بدلًا من القفز عليها كلها في وقت واحد. // bad const items = getItems(), goSportsTeam = true, dragonball = 'z'; // bad // (compare to above, and try to spot the mistake) const items = getItems(), goSportsTeam = true; dragonball = 'z'; // good const items = getItems(); const goSportsTeam = true; const dragonball = 'z'; قم بتجميع كل التعابير const ثم بعدها جمّع التعابير let. لماذا؟ سيكون هذا مفيدًا لاحقًا عندما تحتاج إلى تعيين متغير اعتمادًا على متغير معيّن سابقًا. // bad let i, len, dragonball, items = getItems(), goSportsTeam = true; // bad let i; const items = getItems(); let dragonball; const goSportsTeam = true; let len; // good const goSportsTeam = true; const items = getItems(); let dragonball; let i; let length; عيّن المتغيرات حيث ما أردت، ولكن ضعها في مكان مناسب. لماذا؟ let و const كتلية المدى block scoped وليست دوالية المدى function scoped. // bad - unnecessary function call function checkName(hasName) { const name = getName(); if (hasName === 'test') { return false; } if (name === 'test') { this.setName(''); return false; } return name; } // good function checkName(hasName) { if (hasName === 'test') { return false; } const name = getName(); if (name === 'test') { this.setName(''); return false; } return name; } تجنب الإحالات المتسلسلة للمتغيرات chain variable assignments. لماذا؟ الإحالات المتسلسة للمتغيرات تُنشئ متغيرات كلية. // bad (function example() { // JavaScript interprets this as // let a = ( b = ( c = 1 ) ); // The let keyword only applies to variable a; variables b and c become // global variables. let a = b = c = 1; }()); console.log(a); // throws ReferenceError console.log(b); // 1 console.log(c); // 1 // good (function example() { let a = 1; let b = a; let c = a; }()); console.log(a); // throws ReferenceError console.log(b); // throws ReferenceError console.log(c); // throws ReferenceError // the same applies for `const` تجنب استخدام عمليات الزيادة والإنقاص الأحادية (++, --). لماذا؟ في وثائق eslint، عمليات الزيادة والإنقاص الأحادية تَخضع لقاعدة الإدراج التلقائي للفواصل المنقوطة، ويمكن أن تتسبب في حدوث أخطاء صامتة مع زيادة أو إنقاص قيمة ضمن التطبيق. كما أنه من الأفضل استخدام num += 1 بدلًا من num++ أو num--.تجنُّب استخدام عامل الزيادة والإنقاص الأحادي سيجنبك زيادة قيمة أو إنقاصها بشكل غير مقصود وهو ما يمكن أيضًا أن يسبب سلوكًا غير متوقع في برامجك. // bad const array = [1, 2, 3]; let num = 1; num++; --num; let sum = 0; let truthyCount = 0; for (let i = 0; i < array.length; i++) { let value = array[i]; sum += value; if (value) { truthyCount++; } } // good const array = [1, 2, 3]; let num = 1; num += 1; num -= 1; const sum = array.reduce((a, b) => a + b, 0); const truthyCount = array.filter(Boolean).length; التّرفيع Hoisting يتم ترفيع عبارات التصريح var إلى الأعلى داخل مدى الدالة المحتوية، ولكنّ إحالة قيم على تلك المتغيرات لا يُرفّع. const و let لديهما ميزة جديدة تُسمى Temporal Dead Zones (TDZ). لذلك من المهم أن تعرف لماذا استخدامtypeof لم يعد آمنًا. // we know this wouldn’t work (assuming there // is no notDefined global variable) function example() { console.log(notDefined); // => throws a ReferenceError } // creating a variable declaration after you // reference the variable will work due to // variable hoisting. Note: the assignment // value of `true` is not hoisted. function example() { console.log(declaredButNotAssigned); // => undefined var declaredButNotAssigned = true; } // the interpreter is hoisting the variable // declaration to the top of the scope, // which means our example could be rewritten as: function example() { let declaredButNotAssigned; console.log(declaredButNotAssigned); // => undefined declaredButNotAssigned = true; } // using const and let function example() { console.log(declaredButNotAssigned); // => throws a ReferenceError console.log(typeof declaredButNotAssigned); // => throws a ReferenceError const declaredButNotAssigned = true; } الدوال المجهولة يُرفّع اسم المتغير الخاص بها، بخلاف إحالة الدالة. function example() { console.log(anonymous); // => undefined anonymous(); // => TypeError anonymous is not a function var anonymous = function () { console.log('anonymous function expression'); }; } الدوال المُسماة يُرفّع اسم المتغير الخاص بها، بخلاف الدالة أو متنها. function example() { console.log(named); // => undefined named(); // => TypeError named is not a function superPower(); // => ReferenceError superPower is not defined var named = function superPower() { console.log('Flying'); }; } // the same is true when the function name // is the same as the variable name. function example() { console.log(named); // => undefined named(); // => TypeError named is not a function var named = function named() { console.log('named'); }; } تقوم تصريحات الدوال بترفيع اسم ومتن الدالة. function example() { superPower(); // => Flying function superPower() { console.log('Flying'); } } عمليات المقارنة والتساوي استخدم === و !== بدلًا من == و !=. التعليمات الشرطية مثل العبارة if تُقيّم التعبيرات عن طريق التحويلcoercion بواسطة الدالة المجرّدة ToBoolean، ودائمًا تتبع القواعد البسيطة التالية. Objects يتم إعطاؤها القيمة true Undefined يتم إعطاؤه القيمة false Null يتم إعطاؤه القيمة false Booleans يتم إعطاؤه قيمة العنصر Boolean Numbers يتم إعطاؤه القيمة false إن كان يساوي 0+ أو 0- أو NaN وإلا يُعطى القيمة true Strings يتم إعطاؤها القيمة false إن كانت تساوي "" وإلا تٌعطى القيمة true if ([0] && []) { // true // an array (even an empty one) is an object, objects will evaluate to true } استخدم الاختصارات من أجل القيم المنطقية، بالمقابل استخدم المقارنات الصريحة للنصوص والأرقام. // bad if (isValid === true) { // ... } // good if (isValid) { // ... } // bad if (name) { // ... } // good if (name !== '') { // ... } // bad if (collection.length) { // ... } // good if (collection.length > 0) { // ... } استخدم اللامّات {} لإنشاء الكتل في البنود case و default التي تحتوي عبارات التصريح declarations (على سبيل المثال let، const ، function ، وclass). لماذا؟ عبارات التصريح مرئية في كامل الكتلة switch ولكن لا يتم تهيئتها إلّا عندما يتم إحالة قيمة عليها، وهو ما لا يحدث إلا عند بلوغ case. هذا سيسبب مشاكل عندما تحاول أكثر من case واحدة تعريف الشيء نفسه. // bad switch (foo) { case 1: let x = 1; break; case 2: const y = 2; break; case 3: function f() { // ... } break; default: class C {} } // good switch (foo) { case 1: { let x = 1; break; } case 2: { const y = 2; break; } case 3: { function f() { // ... } break; } case 4: bar(); break; default: { class C {} } } لا ينبغي أن تتداخل التعابير الثلاثية Ternaries وعادةً ينبغي أن تكون على سطر واحد // bad const foo = maybe1 > maybe2 ? "bar" : value1 > value2 ? "baz" : null; // split into 2 separated ternary expressions const maybeNull = value1 > value2 ? 'baz' : null; // better const foo = maybe1 > maybe2 ? 'bar' : maybeNull; // best const foo = maybe1 > maybe2 ? 'bar' : maybeNull; تجنب استخدام التعابير الثلاثية التي لا لزوم لها. // bad const foo = a ? a : b; const bar = c ? true : false; const baz = c ? false : true; // good const foo = a || b; const bar = !!c; const baz = !c; عند مزج العمليات، استخدم الأقواس. والاستثناء الوحيد هي العمليات الحسابية القياسية (+، -، *، /) بما أنّ الأسبقية فيها مفهومة بشكل جيد. لماذا؟ هذا يحسّن المقروئية. // bad const foo = a && b < 0 || c > 0 || d + 1 === 0; // bad const bar = a ** b - 5 % d; // bad // one may be confused into thinking (a || b) && c if (a || b && c) { return d; } // good const foo = (a && b < 0) || c > 0 || (d + 1 === 0); // good const bar = (a ** b) - (5 % d); // good if (a || (b && c)) { return d; } // good const bar = a + b / c * d; الكتل Blocks استخدم اللامّات في حال الكتل متعدد الأسطر. // bad if (test) return false; // good if (test) return false; // good if (test) { return false; } // bad function foo() { return false; } // good function bar() { return false; } إذا كنت تستخدم الكتل متعددة الأسطر مع if و else ، ضع else على نفس السطر الخاص باللّامة التي تغلق كتلة if. // bad if (test) { thing1(); thing2(); } else { thing3(); } // good if (test) { thing1(); thing2(); } else { thing3(); } إذا كانت كتلة if تُرجع دائمًا (أي تنتهي بـ return)، فكتلة else لن تكون ضرورية. عبارة return في كتلة else if موالية لكتلة if والتي تحتوي على return يمكن تقسيمها إلى عدة كتل if. // bad function foo() { if (x) { return x; } else { return y; } } // bad function cats() { if (x) { return x; } else if (y) { return y; } } // bad function dogs() { if (x) { return x; } else { if (y) { return y; } } } // good function foo() { if (x) { return x; } return y; } // good function cats() { if (x) { return x; } if (y) { return y; } } //good function dogs(x) { if (x) { if (z) { return y; } } else { return z; } } عبارات التحكم Control Statements في حال تجاوزت عبارات التحكم (if, while …) الحد الأقصى لطول السطر، فيمكن وضع كل شرط في سطر جديد. كما يجب أن يكون العامل المنطقي في بداية السطر. ● لماذا؟ وضع العوامل operators في بداية السطر يحافظ على محاذاة العوامل ويتبع نمطًا مماثلا لتسلسل الوظائف method chaining. كما يحسن القراءة من خلال توضيح العمليات المنطقية المعقدة. // bad if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) { thing1(); } // bad if (foo === 123 && bar === 'abc') { thing1(); } // bad if (foo === 123 && bar === 'abc') { thing1(); } // bad if ( foo === 123 && bar === 'abc' ) { thing1(); } // good if ( foo === 123 && bar === 'abc' ) { thing1(); } // good if ( (foo === 123 || bar === "abc") && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening() ) { thing1(); } // good if (foo === 123 && bar === 'abc') { thing1(); } التعليقات استخدم /** ... */ لأجل التعاليق متعدد الأسطر. // bad // make() returns a new element // based on the passed in tag name // // @param {String} tag // @return {Element} element function make(tag) { // ... return element; } // good /** * make() returns a new element * based on the passed-in tag name */ function make(tag) { // ... return element; } استخدم // لأجل التعاليق ذات السطر الواحد. ضع التعاليق ذات السطر الواحد على سطر جديد فوق التعليمة البرمجية موضوع التعليق. وضع سطرًا فارغًا قبل التعليق إلا إذا كان على السطر الأول من كتلة block. // bad const active = true; // is current tab // good // is current tab const active = true; // bad function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this.type || 'no type'; return type; } // good function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this.type || 'no type'; return type; } // also good function getType() { // set the default type to 'no type' const type = this.type || 'no type'; return type; } ابدأ كل التعليقات بمسافة لجعلها أسهل للقراءة. // bad //is current tab const active = true; // good // is current tab const active = true; // bad /** *make() returns a new element *based on the passed-in tag name */ function make(tag) { // ... return element; } // good /** * make() returns a new element * based on the passed-in tag name */ function make(tag) { // ... return element; } ابدأ التعليقات التي تهدف إلى لفت انتباه المطورين الآخرين إلى أنّ هناك مشكلةً تحتاج إلى المراجعة بـ FIXME أو TODO، أو إذا كنت تقترح حلّا لتنفيذه. تختلف هذه عن التعليقات العادية لأنها قابلة للتنفيذ. الإجراءات قد تكون: FIXME: -- need to figure this out أو TODO: -- need to implement استخدم // FIXME: للإشارة إلى مشكلة. class Calculator extends Abacus { constructor() { super(); // FIXME: shouldn’t use a global here total = 0; } } استخدم // TODO: لاقتراح حل لمشكلة. class Calculator extends Abacus { constructor() { super(); // TODO: total should be configurable by an options param this.total = 0; } } المسافات Whitespace استخدم مسافتين فارغتين space character لتعيين المسافات. // bad function foo() { ∙∙∙∙let name; } // bad function bar() { ∙let name; } // good function baz() { ∙∙let name; } ضع مسافة قبل اللامّة الأولى. // bad function test(){ console.log('test'); } // good function test() { console.log('test'); } // bad dog.set('attr',{ age: '1 year', breed: 'Bernese Mountain Dog', }); // good dog.set('attr', { age: '1 year', breed: 'Bernese Mountain Dog', }); ضع مسافةً قبل القوس الفاتح في عبارات التحكم (if, while …). لا تضع أي مسافات في لائحة المعاملاتarguments واسم الدالة عند استدعاء الدالة أو تعريفها. // bad if(isJedi) { fight (); } // good if (isJedi) { fight(); } // bad function fight () { console.log ('Swooosh!'); } // good function fight() { console.log('Swooosh!'); } ضع مسافات بين العوامل operators. // bad const x=y+5; // good const x = y + 5; أنهِ الملفات بحرف الرجوع إلى السطر. // bad import { es6 } from './AirbnbStyleGuide'; // ... export default es6; // bad import { es6 } from './AirbnbStyleGuide'; // ... export default es6;↵ ↵ // good import { es6 } from './AirbnbStyleGuide'; // ... export default es6;↵ استخدم المسافة البادئة indentation عند استخدام سلسة طويلة من الوظائف (أكثر من اثنتين). استخدم نقطة في البداية، لكي تبين أنّ السطر هو استدعاء لوظيفة، وليس تعليمةً جديدةً. // bad $('#items').find('.selected').highlight().end().find('.open').updateCount(); // bad $('#items'). find('.selected'). highlight(). end(). find('.open'). updateCount(); // good $('#items') .find('.selected') .highlight() .end() .find('.open') .updateCount(); // bad const leds = stage.selectAll('.led').data(data).enter().append('svg:svg').classed('led', true) .attr('width', (radius + margin) * 2).append('svg:g') .attr('transform', `translate(${radius + margin},${radius + margin})`) .call(tron.led); // good const leds = stage.selectAll('.led') .data(data) .enter().append('svg:svg') .classed('led', true) .attr('width', (radius + margin) * 2) .append('svg:g') .attr('transform', `translate(${radius + margin},${radius + margin})`) .call(tron.led); // good const leds = stage.selectAll('.led').data(data); ضع سطرًا فارغًا بعد الكتل وقبل العبارة الموالية. // bad if (foo) { return bar; } return baz; // good if (foo) { return bar; } return baz; // bad const obj = { foo() { }, bar() { }, }; return obj; // good const obj = { foo() { }, bar() { }, }; return obj; // bad const arr = [ function foo() { }, function bar() { }, ]; return arr; // good const arr = [ function foo() { }, function bar() { }, ]; return arr; لا تبدأ الكتل بأسطر فارغة. // bad function bar() { console.log(foo); } // bad if (baz) { console.log(qux); } else { console.log(foo); } // bad class Foo { constructor(bar) { this.bar = bar; } } // good function bar() { console.log(foo); } // good if (baz) { console.log(qux); } else { console.log(foo); } لا تقم بإضافة مساحات داخل الأقواس. // bad function bar( foo ) { return foo; } // good function bar(foo) { return foo; } // bad if ( foo ) { console.log(foo); } // good if (foo) { console.log(foo); } لا تقم بإضافة مساحات داخل المعقوفات. // bad const foo = [ 1, 2, 3 ]; console.log(foo[ 0 ]); // good const foo = [1, 2, 3]; console.log(foo[0]); قم بإضافة مساحات داخل اللامّات. // bad const foo = {clark: 'kent'}; // good const foo = { clark: 'kent' }; تجنب الأسطر التي يتجاوز طولها 100 حرف (بما في ذلك المسافات). النصوص الطويلة مستثناة من هذه القاعدة، وينبغي ألّا تُفكك. لماذا؟ هذا يضمن سهولة القراءة والصيانة. // bad const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; // bad $.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.')); // good const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; // good $.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' }, }) .done(() => console.log('Congratulations!')) .fail(() => console.log('You have failed this city.')); الفواصل تجنب الفواصل في البداية. // bad const story = [ once , upon , aTime ]; // good const story = [ once, upon, aTime, ]; // bad const hero = { firstName: 'Ada' , lastName: 'Lovelace' , birthYear: 1815 , superPower: 'computers' }; // good const hero = { firstName: 'Ada', lastName: 'Lovelace', birthYear: 1815, superPower: 'computers', }; استخدم فاصلة زائدة إضافية. لماذا؟ هذا يؤدي إلى توضيح الاختلافات بين الأكواد فيgit . أيضًا، المترجمات من أمثال Babel ستزيل الفاصلة الزائدة الإضافية في الكود المترجم، ممّا يعني أنه لا داعي للقلق من مشكلة الفاصلة الزائدة في المتصفحات القديمة. // bad - git diff without trailing comma const hero = { firstName: 'Florence', - lastName: 'Nightingale' + lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'] }; // good - git diff with trailing comma const hero = { firstName: 'Florence', lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'], }; // bad const hero = { firstName: 'Dana', lastName: 'Scully' }; const heroes = [ 'Batman', 'Superman' ]; // good const hero = { firstName: 'Dana', lastName: 'Scully', }; const heroes = [ 'Batman', 'Superman', ]; // bad function createHero( firstName, lastName, inventorOf ) { // does nothing } // good function createHero( firstName, lastName, inventorOf, ) { // does nothing } // good (note that a comma must not appear after a "rest" element) function createHero( firstName, lastName, inventorOf, ...heroArgs ) { // does nothing } // bad createHero( firstName, lastName, inventorOf ); // good createHero( firstName, lastName, inventorOf, ); // good (note that a comma must not appear after a "rest" element) createHero( firstName, lastName, inventorOf, ...heroArgs ); الفاصلة المنقوطة Semicolons نعم لماذا؟ عندما يصادف جافا سكريبت فاصل أسطر line break دون فاصلة منقوطة، فإنه يستخدم مجموعة من القواعد تسمى الإدراج التلقائي للفاصلة المنقوطة (Automatic Semicolon Insertion) لتحديد ما إذا كان يجب اعتبار نهاية السطر كنهاية للتعليمة البرمجية، (كما يوحي الاسم) يقوم جافا سكريبت بوضع فاصلة منقوطة إلى الكود قبل نهاية السطر إذا اعتقد أن التعليمة قد انتهت. الإدراج التلقائي للفاصلة المنقوطة ترافقه بعض السلوكيات الشاذة، وقد يعطب الكود إذا أساء جافا سكريبت تأويل نهاية السطر. ستصبح هذه القواعد أكثر تعقيدًا عندما تضاف الميزات الجديدة إلى جافا سكريبت. قم بإنهاء التعليمات البرمجية بشكل واضح وقم بإعداد أداة linter لتحديد الفواصل المنقوطة المفقودة لتجنب هذه المشاكل. // bad - raises exception const luke = {} const leia = {} [luke, leia].forEach(jedi => jedi.father = 'vader') // bad - raises exception const reaction = "No! That's impossible!" (async function meanwhileOnTheFalcon(){ // handle `leia`, `lando`, `chewie`, `r2`, `c3p0` // ... }()) // bad - returns `undefined` instead of the value on the next line - always happens when `return` is on a line by itself because of ASI! function foo() { return 'search your feelings, you know it to be foo' } // good const luke = {}; const leia = {}; [luke, leia].forEach((jedi) => { jedi.father = 'vader'; }); // good const reaction = "No! That's impossible!"; (async function meanwhileOnTheFalcon(){ // handle `leia`, `lando`, `chewie`, `r2`, `c3p0` // ... }()); // good function foo() { return 'search your feelings, you know it to be foo'; } تحويل الأنواع Type Casting & Coercion قم بتحويل الأنواع في بداية التعليمة. النصوص // => this.reviewScore = 9; // bad const totalScore = new String(this.reviewScore); // typeof totalScore is "object" not "string" // bad const totalScore = this.reviewScore + ''; // invokes this.reviewScore.valueOf() // bad const totalScore = this.reviewScore.toString(); // isn’t guaranteed to return a string // good const totalScore = String(this.reviewScore); بالنسبة للأعداد: استخدم Number لأجل التحويل type casting، أما لتحليل النصوص فاستخدم parseInt دائمًا مع أساس radix . const inputValue = '4'; // bad const val = new Number(inputValue); // bad const val = +inputValue; // bad const val = inputValue >> 0; // bad const val = parseInt(inputValue); // good const val = Number(inputValue); // good const val = parseInt(inputValue, 10); إذا كنت مضطرّا لاستعمال parseInt واحتجت إلى استخدام Bitshiftلأسباب تتعلق بالأداء، فضع تعليقًا يشرح ما الذي تفعله ولماذا. // good /** * parseInt was the reason my code was slow. * Bitshifting the String to coerce it to a * Number made it a lot faster. */ const val = inputValue >> 0; كن حذرًا عند استخدام عمليات الإزاحة bitshift ، فالأعداد مُمثلة ب 64 بتّة. ولكنّ عمليات الإزاحة دائما تُرجع أعدادًا ممثلة ب 32 بتة. الإزاحة يمكن أن تؤدي إلى نتائج غير متوقعة عند استخدام قيم عددية أكبر من 32 بتة. أكبر قيمة للأعداد ذات الإشارة signed numbers الممثلة على 32 بتة هو 2,147,483,647: 2147483647 >> 0; // => 2147483647 2147483648 >> 0; // => -2147483648 2147483649 >> 0; // => -2147483647 القيم المنطقية Booleans: const age = 0; // bad const hasAge = new Boolean(age); // good const hasAge = Boolean(age); // best const hasAge = !!age; اصطلاحات التسمية تجنب الأسماء المكونة من حرف واحد. استخدم أسماءً معبّرة. // bad function q() { // ... } // good function query() { // ... } استخدم أسلوب التسمية camelCase عند تسمية الأشياء، الدوال، والعينات instances. // bad const OBJEcttsssss = {}; const this_is_my_object = {}; function c() {} // good const thisIsMyObject = {}; function thisIsMyFunction() {} لا تستخدم أسلوب التسمية PascalCase إلا عند تسمية المُنشئات أو الأصناف. // bad function user(options) { this.name = options.name; } const bad = new user({ name: 'nope', }); // good class User { constructor(options) { this.name = options.name; } } const good = new User({ name: 'yup', }); لا تستخدم العارضة السفلية في البداية أو النهاية. لماذا؟ لا يوجد في جافا سكريبت مفهوم الخصوصية بالنسبة للخصائص أو الوظائف. على الرغم من أن وضع العارضة السفلية في البداية يعتبره الكثيرون اصطلاحًا لـ “خاص”، إلّا أن هذه الخصائص عامة كلها، وعلى هذا النحو، فهي جزء من الواجهة البرمجية API العامة. هذا الاصطلاح قد يؤدي بالمطورين للاعتقاد خطأً بأن التغيير لن يُعتبر عطبًا في الكود البرمجي، أو أنه ليست هناك حاجة للاختبار. إن بدى لك هذا طويلًا، فتذكر هذه الجملة: إذا كنت تريد لشيء أن يكون “خاصّا”، فيجب ألا تضعه في مكان ملحوظ. // bad this.__firstName__ = 'Panda'; this.firstName_ = 'Panda'; this._firstName = 'Panda'; // good this.firstName = 'Panda'; لا تحفظ مرجعًا لـ this. واستخدم الدوال السهمية أو Function#bind . // bad function foo() { const self = this; return function () { console.log(self); }; } // bad function foo() { const that = this; return function () { console.log(that); }; } // good function foo() { return () => { console.log(this); }; } اسم الملف الأساسي base filename يجب أن يتطابق تمامًا مع اسم التصدير الافتراضي. // file 1 contents class CheckBox { // ... } export default CheckBox; // file 2 contents export default function fortyTwo() { return 42; } // file 3 contents export default function insideDirectory() {} // in some other file // bad import CheckBox from './checkBox'; // PascalCase import/export, camelCase filename import FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase export import InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export // bad import CheckBox from './check_box'; // PascalCase import/export, snake_case filename import forty_two from './forty_two'; // snake_case import/filename, camelCase export import inside_directory from './inside_directory'; // snake_case import, camelCase export import index from './inside_directory/index'; // requiring the index file explicitly import insideDirectory from './insideDirectory/index'; // requiring the index file explicitly // good import CheckBox from './CheckBox'; // PascalCase export/import/filename import fortyTwo from './fortyTwo'; // camelCase export/import/filename import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index" // ^ supports both insideDirectory.js and insideDirectory/index.js استخدم أسلوب التسمية camelCase عندما تقوم بالتصدير الافتراضي لدالة. يجب أن يكون اسم الملف الخاص بك مطابقًا لاسم دالتك. function makeStyleGuide() { // ... } export default makeStyleGuide; استخدم أسلوب التسمية PascalCase عندما تصدّر منشئ / صنف / فردsingleton / مكتبة دوال / أو كائن عارٍ bare object. const AirbnbStyleGuide = { es6: { }, }; export default AirbnbStyleGuide; المختصرات والكلمات المنحوتة initialisms ينبغي أن تُكتب كلها بأحرف كبيرة أو صغيرة. لماذا؟ الأسماء ينبغي أن تكون واضحة للإنسان، وليس لاسترضاء خوارزميات الكمبيوتر. // bad import SmsContainer from './containers/SmsContainer'; // bad const HttpRequests = [ // ... ]; // good import SMSContainer from './containers/SMSContainer'; // good const HTTPRequests = [ // ... ]; // also good const httpRequests = [ // ... ]; // best import TextMessageContainer from './containers/TextMessageContainer'; // best const requests = [ // ... ]; دالّات الدخول Accessors دالّات الدخول ليست ضرورية للخصائص. لا تستخدم الدوال getters أو setters لأنّ لها آثارًا جانبيةً غير متوقعة، ويصعب اختبارها وصيانتها والتعامل معها. بدلًا من ذلك، إذا كنت تريد استخدام دوال الدخول، فاستخدم ()getVal و(’setVal(‘hello . // bad class Dragon { get age() { // ... } set age(value) { // ... } } // good class Dragon { getAge() { // ... } setAge(value) { // ... } } إذا كانت قيمة الخاصية / الوظيفة boolean، استخدم()isVal أو ()hasVal. // bad if (!dragon.age()) { return false; } // good if (!dragon.hasAge()) { return false; } لا ضير في إنشاء دوال ()get و ()set، ولكن يجب أن تكون متسقة. class Jedi { constructor(options = {}) { const lightsaber = options.lightsaber || 'blue'; this.set('lightsaber', lightsaber); } set(key, val) { this[key] = val; } get(key) { return this[key]; } } الأحداث عند ربط حمولات البيانات بالأحداث (مثل أحداث DOM أو غيرها، مثل أحداث Backbone)، قم بتمرير كائن حرفي object literal (معروف أيضًا باسم “hash”) بدلًا من قيمة خام raw value. هذا سيسمح لاحقًا بإضافة المزيد من البيانات إلى حَمولة الحدث دون الحاجة إلى إيجاد وتحديث كل معالجات الحدث. على سبيل المثال، بدلًا من: // bad $(this).trigger('listingUpdated', listing.id); // ... $(this).on('listingUpdated', (e, listingID) => { // do something with listingID }); من الأفضل استخدام: // good $(this).trigger('listingUpdated', { listingID: listing.id }); // ... $(this).on('listingUpdated', (e, data) => { // do something with data.listingID }); jQuery ضع السابقة $ قبل متغيراتjQuery . // bad const sidebar = $('.sidebar'); // good const $sidebar = $('.sidebar'); // good const $sidebarBtn = $('.sidebar-btn'); خزّن عمليات البحث المؤقت لـ jQuery. // bad function setSidebar() { $('.sidebar').hide(); // ... $('.sidebar').css({ 'background-color': 'pink', }); } // good function setSidebar() { const $sidebar = $('.sidebar'); $sidebar.hide(); // ... $sidebar.css({ 'background-color': 'pink', }); } بالنسبة لاستعلامات DOM استخدم $('.sidebar ul') أو parent > child $('.sidebar > ul'). استخدم find مع استعلامات jQuery المحدّدة scoped jQuery object queries)). // bad $('ul', '.sidebar').hide(); // bad $('.sidebar').find('ul').hide(); // good $('.sidebar ul').hide(); // good $('.sidebar > ul').hide(); // good $sidebar.find('ul').hide(); المكتبة القياسية المكتبة القياسية تحتوي بعض الأدوات المساعدة التي لم تعد تُستخدم ولكن يتم الإبقاء عليها لأجل المتصفحات القديمة. استخدم Number.isNaN بدلًا من isNaN. لماذا؟ isNaN تحوّل القيم غير العددية إلى أعداد، وتُرجع القيمة true لأي شيء يتحوّل إلى NaN. إذا كان هذا ما تريد، فاجعله صريحًا إذًا. // bad isNaN('1.2'); // false isNaN('1.2.3'); // true // good Number.isNaN('1.2.3'); // false Number.isNaN(Number('1.2.3')); // true استخدم Number.isFinite بدلًا من isFinite. لماذا؟ isFinite تحوّل القيم غير العددية إلى أعداد، وتُرجع القيمة true لأي شيء يتحوّل إلى عدد منتهٍ. إذا كان هذا ما تريد، فاجعله صريحًا إذًا. // bad isFinite('2e3'); // true // good Number.isFinite('2e3'); // false Number.isFinite(parseInt('2e3', 10)); // true الاختبار نعم function foo() { return true; } مهما كان إطار الاختبار الذي تستخدمه، يجب أن تكتب الاختبارات بنفسك! احرص على كتابة العديد من الدوال البسيطة والصغيرة، وقلّل من استخدام البيانات المتحوّلةmutations . كن حذرًا عند استخدام أصناف stubs و mocks – لأنها يمكن أن تجعل اختباراتك أكثر هشاشة. محاولة اختبار 100٪ من الكود هو هدف جيد، حتى لو لم يكن دائمًا عمليًّا. كلما أصلحت خللًا، قم بكتابة اختبار تقييم regression test. فبدونه من المؤكد أنّ الثغرات ستعود مجدّدًا. ترجمة -وبتصرّف- للمقال Airbnb JavaScript Style Guide
  5. التخزين الكائني Object storage هي وسيلة شائعة لتخزين ومعالجة الأصول الثابتة مثل الصوت والصور والنصوص وملفات PDF، وأنواع أخرى من البيانات غير المُهيكلة. مقدمو الخدمات السحابية يعرضون خدمات التخزين الكائني بالإضافة إلى التخزين المحلي أو التخزين الكُتلي Block storage، والذي يُستخدم لتخزين ملفات التطبيقات الديناميكية وقواعد البيانات. يمكنك مطالعة المقال مقارنة بين خدمات التخزين الكائني والتخزين الكتلي لمعرفة حالات الاستخدام والاختلافات بين المقاربتين. Spaces هي خدمة تخزين كائني تقدمها DigitalOcean. فبالإضافة إلى إمكانية تسجيل الدخول ورفع وإدارة وحذف الملفات المخزنة بواسطة لوحة التحكم، يمكنك أيضًا الوصول إلى مساحة تخزين DigitalOcean عبر سطر الأوامر وعبر الواجهة البرمجية Spaces API. في هذا الدرس، سوف نُنشئ تطبيق Node.js يسمح للمستخدمين برفع الملفات إلى مساحة التخزين في DigitalOcean عن طريق ملء استمارة في الواجهة الأمامية من الموقع. المتطلبات الأساسية لمتابعة هذا الدرس، سوف تحتاج: مساحة تخزين في DigitalOcean، إضافة إلى مفتاح وصول access keyومفتاح وصول سرّي لحسابك. يمكنك مطالعة الدليل How To Create a DigitalOcean Space and API Keyلإعداد حساب على DigitalOcean، وإنشاء مساحة التخزين خاصتك وإنشاء مفتاح API ورمز سري. تحتاج كذلك إلى تثبيت Node.js و npmعلى جهازك. يمكنك الذهاب إلى الموقع الرسمي لـ Node.js لتثبيت الإصدار المناسب لنظام التشغيل الخاص بك. يجب أن يكون لديك الآن حساب DigitalOcean ومساحة تخزين مع مفتاح الوصول access key إضافة إلى Node.js وnpm مُثبتين على جهازك. إضافة مفاتيح الوصول إلى ملف الاعتماد Credentials File مساحات التخزين في DigitalOcean متوافقة مع الواجهة البرمجية API لـ Amazon Simple Storage Service (S3) ، سنستخدم الخدمة SDK AWS الخاصة بجافاسكريبت في Node.js لربط الاتصال بمساحة التخزين التي أنشأناها. الخطوة الأولى هي إنشاء ملف الاعتماد credentials file، لوضع مفتاح الوصول access key ومفتاح الوصول السري secret access keyالذي حصلت عليه عند إنشاء مساحة التخزين الخاصة بك في DigitalOcean. سيتم وضع الملف في aws/credentials./~ على الماك و اللينكس، أو في C:\Users\USERNAME\.aws\credentials على ويندوز. افتح مُوجه الأوامر command prompt، تأكد من أنك في مجلد المستخدمين Users directory الخاص بك، وأنك تملك صلاحيات استخدام sudo، ثم قم بإنشاء مجلد .aws مع تضمينه ملف الاعتماد. sudo mkdir .aws && touch .aws/credentials افتح الملف، وألصق الكود البرمجي التالي داخله، مع استبدال your_access_key و your_secret_key بمفاتيحك الخاصة. credentials [default] aws_access_key_id=your_access_key aws_secret_access_key=your_secret_key الآن سوف تتم المصادقة على دخولك إلى مساحة التخزين Spaces عبر SDK AWS، ويمكننا الآن الانتقال إلى إنشاء التطبيق. تثبيت ارتباطات Node.js للبدء، قم بإنشاء مجلد لوضع تطبيق Node.js فيه ثم اذهب إليه. في هذا المثال التوضيحي، سوف نقوم بإنشاء مشروعنا في spaces-node-app في المجلد sites. mkdir sites/spaces-node-app && cd sites/spaces-node-app قم بإنشاء ملف جديد package.json لأجل مشروعك. وألصق داخله الكود أدناه. package.json { "name": "spaces-node-app", "version": "1.0.0", "main": "server.js", "scripts": { "start": "node server.js" }, "license": "MIT" } الملف package.json سيتضمّن الاسم، رقم الإصدار وترخيص استعمال التطبيق. أما الحقلscripts فسيسمح لنا بتشغيل خادم Node.js بكتابة npm start بدلًا من node server.js. سوف نقوم بتثبيت كل ارتباطاتنا dependencies بالتعليمة npm install، تليها أسماء الارتباطات الأربع في مشروعنا. npm install aws-sdk express multer multer-s3 بعد تشغيل هذا الأمر، سيتم تحديث الملف package.json. هذه الارتباطات ستساعدنا على ربط الاتصال بالواجهة البرمجية لمساحة التخزين فيDigitalOcean ، وإنشاء خادم الويب، والتعامل مع رفع الملفات. ● aws-sdk - ستسمح لنا AWS SDK لجافاسكريبت بالوصول إلى S3 من خلال الواجهة البرمجية لجافاسكريبت (JavaScript API). ● express - Express هي بيئة تطوير للشبكة تسمح لنا بإعداد الخادم بسرعة وكفاءة. ● multer - Multer هو برنامج وسيط لمعالجة رفع الملفات. ● multer-s3 - Multer S3 يمدد رفع الملفات لـ S3 object storage، والتي هي في حالتنا، مساحة تخزين DigitalOcean. الآن وبعد أن أعددنا مكان وارتباطات مشروعنا، يمكن إعداد الخادم والمنظر الأمامي front-end views. إنشاء الواجهة الأمامية للتطبيق أولًا، سننشئ عدة ملفات لأجل المناظر العامّة public views لتطبيقنا. وهي التي سيراها المستخدم على الواجهة الأمامية. قم بإنشاء مجلد اسمهpublic يتضمن index.html, success.html و error.html. حيث ستحتوي كل هذه الملفات الثّلاث على هيكل HTML أدناه، لكن مع محتويات مختلفة في body. قم بكتابة الكود البرمجي التالي في كل ملف. <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>DigitalOcean Spaces Tutorial</title> <link rel="stylesheet" href="./style.css"> </head> <body> <!-- contents will go here --> </body> </html> أكتب رسالة خطأ في body داخل error.html. error.html ... <h1>Something went wrong!</h1> <p>File was not uploaded successfully.</p> ... أكتب رسالة نجاح فيbody داخل success.html. success.html ... <h1>Success!</h1> <p>File uploaded successfully.</p> ... في index.html، سنقوم بإنشاء استمارة HTML بـ multipart/form-data. وستتكون من مُدخل input بسيط وزر الإرسال submit. index.html ... <h1>DigitalOcean Spaces Tutorial</h1> <p>Please select a file and submit the form to upload an asset to your Dig-italOcean Space.</p> <form method="post" enctype="multipart/form-data" action="/upload"> <label for="file">Upload a file</label> <input type="file" name="upload"> <input type="submit" class="button"> </form> ... وأخيرا، سنقوم بإنشاء ملف style.css وإضافة تنسيقات CSS لجعل التطبيق سهل القراءة. style.css html { font-family: sans-serif; line-height: 1.5; color: #333; } body { margin: 0 auto; max-width: 500px; } label, input { display: block; margin: 5px 0; } بهذه الملفات الثلاث، لدينا الآن استمارة لرفع الملفات والتي ستشكل ملامح الصفحة الرئيسية لتطبيقنا الصغير هذا، كما لدينا أيضًا صفحة للنجاح والخطأ لأجل المستخدم. إعداد Express Server Environment لقد أنشأنا كل الملفات الخاصة بالواجهة الأمامية لتطبيقنا، لكننا حاليا لم نقم بعدُ بإعداد أي خادم أو أي طريقة لعرضها. سنقوم بإعداد خادمNode بواسطة Express web framework. في المجلد الجذري للمشروع، قم بإنشاء ملف server.js. وفي الجزء العلوي، قم برفع أربعة ارتباطات بواسطة الدالة ()require. كما سنُوجه تطبيقنا عبر عيّنة app من express. server.js // Load dependencies const aws = require('aws-sdk'); const express = require('express'); const multer = require('multer'); const multerS3 = require('multer-s3'); const app = express(); الواجهة الأمامية موجودة في المجلدpublic ، لذلك ضع الإعدادات تحت الارتباطات مباشرة. server.js ... // Views in public directory app.use(express.static('public')); سوف نوجّه index.html, success.html و error.html نسبة إلى جذر الخادم root of the server. server.js ... // Main, error and success views app.get('/', function (request, response) { response.sendFile(__dirname + '/public/index.html'); }); app.get("/success", function (request, response) { response.sendFile(__dirname + '/public/success.html'); }); app.get("/error", function (request, response) { response.sendFile(__dirname + '/public/error.html'); }); وأخيرا، سوف نخبر الخادمَ أيّ منفذport يجب أن يُصغي إليه. في هذا المثال، سنستخدم 3001, ولكن يمكنك تعيين أي منفذ متاح. server.js ... app.listen(3001, function () { console.log('Server listening on port 3001.'); }); احفظ server.js وشغّل الخادم. يمكنك القيام بذلك عن طريق تشغيل node server.js، أو بـ npm start، الاختصار الذي وضعناه في package.json. npm start Output > node server.js Server listening on port 3001. تصفّح http://localhost:3001، وسوف ترى استمارة الرفع، لأننا جعلنا index.html جذرَ الخادم. يمكنك أيضًا تصفّح http://localhost:3001/success و http://localhost:3001/error للتحقق من أن تلك الصفحات تُوجّه بشكل صحيح. رفع ملف إلى مساحة التخزين بواسطة Multer الآن بعد أن أعددنا بيئة الخادم بشكل صحيح، الخطوة الأخيرة هي دمج الاستمارة مع Multer وMulter S3 لأجل رفع الملف إلى مساحة التخزين. يمكنك استخدام ()new aws.S3 لربط الاتصال بـعميل أمازون( S3 (Amazon S3 client. ولاستخدامه مع Spaces DigitalOcean، سنحتاج إلى تحديد نقطة مرجعيةendpoint جديدة للتأكد من أنه يرفع الملفات إلى المكان الصحيح. في وقت كتابة هذا الدرس، nyc3 هي المنطقة الوحيدة المتاحة لـمساحة التخزين Spaces. في الملف server.js، انتقل مرة أخرى إلى أعلى وقم بإلصاق الأكواد التالية مباشرة تحت التصريح بالثوابت. server.js ... const app = express(); // Set S3 endpoint to DigitalOcean Spaces const spacesEndpoint = new aws.Endpoint('nyc3.digitaloceanspaces.com'); const s3 = new aws.S3({ endpoint: spacesEndpoint }); باستخدام المثال الموجود في وثائق multer-S3، سوف نقوم بإنشاء دالة upload، وإحالة اسم مساحة التخزين خاصتك إلى الخاصية bucket. قم بإعطاء القيمة acl للخاصية public-read لضمان أن يكون الملف في متناول الجميع. لكن إن تركت تلك الخاصية فارغة فستأخذ القيمية الافتراضية private، مما يجعل الملفات غير مُتاحة على شبكة الإنترنت. server.js ... // Change bucket property to your Space name const upload = multer({ storage: multerS3({ s3: s3, bucket: 'your-space-here', acl: 'public-read', key: function (request, file, cb) { console.log(file); cb(null, file.originalname); } }) }).array('upload', 1); بعد اكتمال الدالة upload، الخطوة الأخيرة هي وَصل استمارة الرفع بالكود البرمجي لإرسال الملف وتوجيه المستخدم وفقا لذلك. انتقل إلى أسفل الملف server.js، وألصق هذا الكود مباشرة فوق الوظيفة ()app.listen في نهاية الملف. server.js ... app.post('/upload', function (request, response, next) { upload(request, response, function (error) { if (error) { console.log(error); return response.redirect("/error"); } console.log('File uploaded successfully.'); response.redirect("/success"); }); }); عندما ينقر المستخدم على submit سيتم إرسال استعلامPOST إلى upload/, سيكون Node في حالة إصغاء/انتظار لـ POST. وسيستدعي الدالة()upload. وفي حال العثور على خطأ، فستُعيد العبارة الشرطية توجيه المستخدم إلى الصفحة error/. أما إذا مرّت الأمور بشكل جيد، فستتم إعادة توجيه المستخدم إلى الصفحة success/، وسيتم رفع الملف إلى مساحة التخزين خاصتك. هذا هو الكود الكامل لـ server.js. server.js // Load dependencies const aws = require('aws-sdk'); const express = require('express'); const multer = require('multer'); const multerS3 = require('multer-s3'); const app = express(); // Set S3 endpoint to DigitalOcean Spaces const spacesEndpoint = new aws.Endpoint('nyc3.digitaloceanspaces.com'); const s3 = new aws.S3({ endpoint: spacesEndpoint }); // Change bucket property to your Space name const upload = multer({ storage: multerS3({ s3: s3, bucket: 'your-space-here', acl: 'public-read', key: function (request, file, cb) { console.log(file); cb(null, file.originalname); } }) }).array('upload', 1); // Views in public directory app.use(express.static('public')); // Main, error and success views app.get('/', function (request, response) { response.sendFile(__dirname + '/public/index.html'); }); app.get("/success", function (request, response) { response.sendFile(__dirname + '/public/success.html'); }); app.get("/error", function (request, response) { response.sendFile(__dirname + '/public/error.html'); }); app.post('/upload', function (request, response, next) { upload(request, response, function (error) { if (error) { console.log(error); return response.redirect("/error"); } console.log('File uploaded successfully.'); response.redirect("/success"); }); }); app.listen(3001, function () { console.log('Server listening on port 3001.'); }); أوقف خادم Node عبر الضغط على CONTROL + C في موجه الأوامر، وأعد تشغيله لضمان تطبيق التغييرات الجديدة. npm start انتقل إلى جذر المشروع، اختر ملفَا، وقم بتفعيل الاستمارة. إذا تم تعيين كل شيء بشكل صحيح، سيتم توجيهك إلى صفحة النجاح success page، وسوف يكون الملف متاحًا للعموم في مساحة التخزين خاصتك في DigitalOcean. بافتراض أن الملف الذي قمت برفعه هو test.txt، فإن عنوان الملف سيكون: https://your-space-here.nyc3.digitaloceanspaces.com/test.txt من الأسباب الشائعة لفشل العمليات هي الاعتمادات credentials الخاطئة، وضع ملفات الاعتماد في أماكن غير صحيحة أو استخدام اسم bucket غير صحيح. الخلاصة تهانينا، لقد أنشأت تطبيق Node.js وExpress لرفع الأصول الثابتة للتخزين الكائني object storage. يمكن أن تقوم ببعض التعديلات والتجارب على التطبيق من هنا. لا بد من أخذ احتياطات إضافية مثل المصادقة authentication لضمان سير جيد لهذا النوع من التطبيقات، وتبقى هذه نقطة انطلاق جيدة لجعل تطبيق الويب خاصتك يعمل بسلاسة معDigitalOcean Spaces . ترجمة -وبتصرّف- للمقال How To Upload a File to Object Storage with Node لصاحبته Tania Rascia
  6. مقدمة Node.js هي منصة لجافا سكريبت للبرمجة متعددة الأغراض والتي تسمح للمستخدمين ببناء تطبيقات الشبكة بسرعة. من خلال استخدام جافا سكريبت على كل من الواجهة الأمامية والخلفية، سيكون التطوير أكثر اتساقا وسيُصمم داخل النظام نفسه. في هذا الدليل، ستتعلم كيفية تثبيت Node.js على خادم Debian 8. يحتوي Debian 8 على إصدار لـ Node.js في مجلداته الافتراضية، ولكن ذلك الإصدار قديم في الأغلب، سنقوم باستكشاف طريقتين لتثبيت أحدث إصدارات Node.js على نظامك. المتطلبات الأساسية لمتابعة هذا الدرس، ستحتاج إلى خادم Debian 8 مع مستخدم غير كامل الصلاحيات non-root user ويملك امتيازات sudo. كيفية التثبيت باستخدام PPA أسرع وأسهل وسيلة للحصول على أحدث إصدار من Node.js على خادمك هي بإضافة PPA (personal package archive) الخاص بـ NodeSource. سوف يشتمل على عدد أكبر من تحديثات Node.js مقارنة بمستودعات Debian الرسمية. كما أنه يتيح لك الاختيار بين Node.js v4.x (النسخة القديمة المدعومة على المدى الطويل, مدعومة حتى أبريل 2017)، v6.x (نسخة أحدث من LTS، والتي ستُدعم حتى أبريل 2018)، و Node.js v7.x (النسخة الحالية قيد التطوير). أولا، قم بتثبيت PPA للحصول على محتوياته. تأكد من أنك في المجلد الرئيسي home directory، استخدم curl لاستخراج النص البرمجي لتثبيت الإصدار المفضل لديك، مع استبدال 6.x برقم الإصدار الصحيح: cd ~ curl -sL https://deb.nodesource.com/setup_6.x -o nodesource_setup.sh يمكنك فحص محتويات هذا النص البرمجي بواسطة nano (أو محرر النصوص المفضل لديك): nano nodesource_setup.sh وقم بتشغيل البرنامج النصي عقِب الأمر sudo: sudo bash nodesource_setup.sh سيتم إضافة PPA إلى إعداداتك وسوف يتم تحديث حزمتك المحلية المُخزنة تلقائيًا. بعد تشغيل برنامج التنصيب من nodesource، يمكنك تثبيت حزمة Node.js بنفس الطريقة التي اتبعتها أعلاه: sudo apt-get install nodejs الحزمة nodejs تحتوي رُقامة nodejs (nodejs binary ) إضافة إلى npm، لذلك لا تحتاج إلى تثبيت npm بشكل منفصل. ولكن لكي تعمل بعض حُزم npm (مثل تلك التي تتطلب ترجمة التعليمات البرمجية من المصدر)، فستحتاج إلى تثبيت الحزمة build-essential: sudo apt-get install build-essential كيفية التثبيت بواسطة nvm بدل تثبيت Node.js بواسطة apt، يمكنك استعمال أداة خاصة تسمّى nvm (Node.js version manager). فباستخدام nvm، يمكنك تثبيت عدة إصدارات متكاملة من Node.js ما سيسمح لك بضبط بيئة العمل بشكل أسهل. كما ستعطيك إمكانية الوصول إلى أحدث إصدارات Node.js، ولكن ستسمح لك أيضا باستهداف الإصدارات السابقة التي قد يعتمد عليها تطبيقك. بدايةً سوف نحتاج إلى الحصول على حزم البرمجيات من مستودعات Debian والتي ستسمح لنا ببناء الحُزم المصدرية source packages. التعليمة nvm ستستخدم تلك الأدوات لبناء المكونات الضرورية: sudo apt-get update sudo apt-get install build-essential libssl-dev حالما يتم تثبيت الحُزم الضرورية، يمكنك حذف سكريبت التثبيتnvm من صفحة المشروع علىGitHub , رقم الإصدار قد يكون مختلفًا، ولكن بشكل عام، يمكنك تحميله بـ curl: curl -sL https://raw.githubusercontent.com/creationix/nvm/v0.32.0/install.sh -o install_nvm.sh طالع سكريبت التثبيت بواسطة nano: nano install_nvm.sh قم بتشغيل النص البرمجي بواسطة bash: bash install_nvm.sh النص البرمجي سيقوم بتثبيت البرنامج في مجلد في ~/.nvm. كما سيقوم بإضافة الأسطر اللازمة للملف ~/.profile لجعل التعليمة nvm متاحًا. لكسب إمكانية الوصول إلى التعليمةnvm وكذلك وظائفها، ستحتاج إلى تسجيل الخروج ثم تسجيل الدخول مرة أخرى، أو يمكنك إضافة المصدر ~/.profile حتى يتم اعتبار التغييرات في الجلسةsession الحالية: source ~/.profile الآن وبعد تثبيت nvm، يمكنك تثبيت أحد إصدارات Node.js بشكل منفصل. لمعرفة إصدارات Node.js المتوفرة للتثبيت، قم بكتابة: nvm ls-remote Output ... v6.8.0 v6.8.1 v6.9.0 (LTS: Boron) v6.9.1 (LTS: Boron) v6.9.2 (Latest LTS: Boron) v7.0.0 v7.1.0 v7.2.0 كما ترون، فأحدث إصدار في وقت كتابة هذه السطور هو v7.2.0، ولكن v6.9.2 هو آخر الإصدارات المدعومة على المدى الطويل. يمكنك تثبيته بكتابة: nvm install 6.9.2 سوف ترى المخرجات التالية: Computing checksum with sha256sum Checksums matched! Now using node v6.9.2 (npm v3.10.9) Creating default alias: default -> 6.9.2 (-> v6.9.2) عادة، سيتحولnvm إلى استخدام الإصدار المثبت حديثًا. لكن يمكنك أن تخبرnvm صراحة باستخدام النسخة التي حمّلناها للتو عن طريق كتابة: nvm use 6.9.2 يمكنك مشاهدة النسخة المستخدمة حاليا عن طريق كتابة في واجهة الأوامر: node -v Output v6.9.2 إن كان لديك عدة إصدارات من Node.js، يمكنك معرفة أيّ منها تم تثبيته بكتابة: nvm ls إذا كنت ترغب في جعل أحد هذه الإصدارات نسختك الافتراضية، يمكنك كتابة: nvm alias default 6.9.2 هذا الإصدار سيتم اختياره تلقائيًا عند فتح جلسة عمل جديدة على المِطراف terminal. يمكنك أيضًا الإحالة إليه باستخدام الاسم default هكذا: nvm use default كل نسخة من Node.js ستُتابع حُزمها الخاصة بها، وسيكونnpm متاحًا لإدارتها. يمكنك وضع حُزم تثبيت npm في المجلد ./node_modules الخاص بمشروعNode.js باستخدام الصيغة الطبيعية normal format. على سبيل المثال، بالنسبة للوحدة express: npm install express إذا كنت ترغب بتثبيته بشكل كلّي (جعْله متاحا للمشاريع الأخرى التي تستخدم نفس إصدار Node.js)، يمكنك إضافة العلم –g كما يلي: npm install -g express هذا سيثبّت الحزمة في: ~/.nvm/node_version/lib/node_modules/package_name التثبيت الكلي سيتيح لك تشغيل التعليمات من سطر الأوامر، ولكن سيكون عليك ربط الحزمة من مجالك المحلي local sphere للوصول إليه من داخل برنامج ما: npm link express يمكنك معرفة المزيد حول الخيارات المتاحة لك مع nvm بكتابة: nvm help خلاصة كما رأيت، هناك عدة طرق لتثبيت وتشغيل Node.js على خادم Debian 8. ظروفك ستملي عليك أيّ الطرق المذكورة أعلاه ستكون أفضل لك. وفي حين أن النسخة المحزومة packaged في مستودع Ubuntu هي الأسهل، إلّا أن طريقةnvm أكثر مرونة بالتأكيد. ترجمة -وبتصرّف- للمقال How To Install Node.js on Debian 8 لصاحبه Brian Hogan
  7. في هذا الدرس سنتعلّم كيفية استخدام بيئات التطوير ومكتبات أندرويد الشهيرة التي تستخدم التأشير annotation processing في Kotlin. يوجد في عالم الأندرويد العديد من بيئات التطوير الشهيرة التي تبسّط التطوير. يمكنك استخدام نفس البيئات لأجل التطوير في Kotlin كما تفعل في جافا. يقدم هذا الدرس بعض الأمثلة ويسلّط الضوء على الفروقات في الإعدادات. سنلقي نظرة على Dagger, Butterknife, Data Binding, Auto-parcel و DBFlow (بقية بيئات التطوير يمكن إعدادها بشكل مماثل). كل بيئات التطوير هذه تعمل بنظام التأشير annotation processing: حيث تقوم بالتأشير على الشيفرة البرمجيّة وسيتمّ توليد أكواد جاهزة لأجلك. تقلّل التأشيرات من الإسهاب غير الضروري وتبسّط الشيفرة البرمجية، وإن كنت تريد أن تفهم ما الذي يحدث فعلا في وقت التنفيذ runtime، يمكنك إلقاء نظرة على الشيفرة التي تم إنشاؤها. تذكّر أن كل بيئات التطوير هذه تولّد الأكواد في جافا، وليس Kotlin. في Kotlin تقوم بتحديد الارتباطات (dependencies بطريقة مماثلة لجافا باستخدام Kotlin Annotation processing tool (kapt بدلاً من annotationProcessor. Dagger Dagger هو بيئة تطوير تحقن الارتباطات dependency injection. إن لم يكن مألوفًا لديك، يمكنك القراءة عنه في دليل المستخدم. لقد حوّلنا مثال القهوة المذكور في هذا الدليل إلى Kotlin، ويمكنك إيجاد النتيجة هنا. الشيفرة البرمجية لـ Kotlin تبدو مشابهة إلى حد كبير للشيفرة الأصليّة. يمكنك تصفح المثال كله في ملف واحد. كما هو الحال في جافا، يمكنك استخدامInject@ للتأشير على المنشئ constructor المستخدم من قبل Dagger لإنشاء عيّنات instancesمن صنف ما. لدى Kotlin أسلوب مختصر للإعلان عن خاصية ما وعن معامل (parameter) منشئ ما في نفس الوقت. ولكي تؤشّر على المنشئ، استخدم الكلمة constructor بشكل صريح وضَع التأشير Inject@ قبله: class Thermosiphon @Inject constructor( private val heater: Heater ) : Pump { // ... } التأشير على الوظائف methods يبدو تمامًا بنفس الشكل. في المثال التالي Binds@ تحدّد أنّ الكائن Thermosiphon سيستخدم حيثما كان Pump مطلوبًا، تحدّد Provides@ طريقة بناء Heater، كما توضّح Singleton@ أنّ Heater نفسه ينبغي أن يستخدم في كل مكان: @Module abstract class PumpModule { @Binds abstract fun providePump(pump: Thermosiphon): Pump } @Module(includes = arrayOf(PumpModule::class)) class DripCoffeeModule { @Provides @Singleton fun provideHeater(): Heater = ElectricHeater() } الأصناف المؤشّرة بـ Module@ تحدّد كيفية توفير مختلف الكائنات. انتبه إلى أنه عند تمرير معامل تأشير (annotation argument) كمعامل vararg، فسيكون عليك أن تغلّفه بـ arrayOf، كما هو الحال في (Module(includes = arrayOf(PumpModule::class)@ أعلاه. لكي يتمّ توليد تطبيق محقون الارتباط (dependency-injected implementation) لأجل نوع ما, قم بالتأشير عليه بـ Component@. الصّنف المُولّد سيكون له اسم هذا النوع مسبوقًا بـ Dagger، مثل DaggerCoffeeShop أدناه: @Singleton @Component(modules = arrayOf(DripCoffeeModule::class)) interface CoffeeShop { fun maker(): CoffeeMaker } fun main(args: Array<String>) { val coffee = DaggerCoffeeShop.builder().build() coffee.maker().brew() } يقوم Dagger بتوليد تطبيق لـ CoffeeShop والذي يسمح لك بالحصول على صنف CoffeeMaker محقون بالكامل. يمكنك التنقل ومشاهدة تطبيق DaggerCoffeeShop إذا قمت بفتح المشروع في بيئة التطوير. لاحظنا أن التأشير على الأكواد البرمجية لم يتغيّر تقريبًا عند التحوّل إلى Kotlin. الآن دعونا نرى التغييرات التي ينبغي إدخالها على نص البناء البرمجي. في جافا تقوم بربط Dagger بـ annotationProcessor (أو apt): dependencies { ... annotationProcessor "com.google.dagger:dagger-compiler:$dagger-version" } في Kotlin عليك إضافة ملحقة kotlin-kapt لإتاحة kapt, ثم قم باستبدال annotationProcessor بـ kapt: apply plugin: 'kotlin-kapt' dependencies { ... kapt "com.google.dagger:dagger-compiler:$dagger-version" } هذا كل شيء! لاحظ أن kapt يعتني بملفات جافا كذلك، لذلك لا تحتاج الإبقاء على الارتباط بـ annotationProcessor. النص البرمجي الكامل لبناء لهذا المشروع يمكن العثور عليه هنا . يمكنك أيضا الاطّلاع على الكود البرمجي المحوَّل لأندرويد. ButterKnife يسمح ButterKnife بربط العروض views بالحقول مباشرة بدلاً من استدعاء findViewById. لاحظ أن ملحقات أندرويد الإضافية لـ Kotlin (مُدمجة تلقائيًا في ملحقة Kotlin في أندرويد ستوديو) تحل المسألة نفسها: استبدال findViewById بكود برمجي موجز وواضح. ربّما عليك استخدامه إلا إذا كنت تستخدم ButterKnife سلفًا ولا تنوي الهجرة. يمكنك استخدام ButterKnife مع Kotlin بنفس طريقة استخدامه مع جافا. دعونا نرى أوّلا التغيرات التي طرأت على نص البناء البرمجي في Gradle، ومن ثم تسليط الضوء على بعض الاختلافات في الكود البرمجي. في ارتباطات Gradle استخدم الملحقة kotlin-kapt وقم باستبدال annotationProcessor بـ kapt: apply plugin: 'kotlin-kapt' dependencies { ... compile "com.jakewharton:butterknife:$butterknife-version" kapt "com.jakewharton:butterknife-compiler:$butterknife-version" } لقد قمنا بتحويل عيّنة ButterKnife إلى Kotlin. يمكن العثور على الكود الناتج هنا. دعونا نلقي نظرة أكثر عليه لمعرفة ما الذي تغيّر. في جافا تؤشّر على الحقل وتربطه بالعرض view المقابل: @BindView(R2.id.title) TextView title; في Kotlin لا يمكنك العمل مع الحقول مباشرة، ولكن بدل ذلك يمكنك العمل مع الخصائص. حيث تؤشّر على الخاصّية: @BindView(R2.id.title) lateinit var title: TextView يتم تعريف التأشير BindView@ ليتم تطبيقه على الحقول فقط، مُترجم Kotlin يدرك هذا ويقوم بالتأشير على الحقل المقابل تلقائيا عندما تقوم بالتأشير على كامل الخاصيّة. لاحظ كيف أن مُعدِّل lateinit يسمح بالتصريح بأنواع غير معدومةnon-null مُبتدَأً (initialized) بعد إنشاء الكائن (بعد استدعاء المنشئ). بدون lateinit سيكون عليك أن تُصرّح بنوع ذي قيمة معدومةً (nullable type) وتقوم بإضافة تحقيقات إضافية لاختبار العدميّة nullability. يمكنك أيضًا إعداد الوظائف كمُصغيات listeners، باستخدام تأشيرات ButterKnife: @OnClick(R2.id.hello) internal fun sayHello() { Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show() } يحدد هذا الكود إجراءً ليتم تنفيذه عند النقر على الزر “hello”. لاحظ أنه باستخدام صيغة lambdas فإن الكود يبدو موجزًا عند كتابته مباشرةً في Kotlin: hello.setOnClickListener { toast("Hello, views!") } الدالّة toast مُعرّفة في المكتبة Anko . ربط البيانات مكتبة ربط البيانات تسمح لك بربط بيانات تطبيقك بالخُطاطةlayouts بطريقة موجزة. يمكنك إتاحة المكتبة باستخدام نفس الإعدادات في جافا: android { ... dataBinding { enabled = true } } لجعله يعمل مع أصناف Kotlin قم بإضافة الارتباط kapt: apply plugin: 'kotlin-kapt' dependencies { kapt "com.android.databinding:compiler:$android_plugin_version" } عند التبديل إلى Kotlin، لا تتغير ملفات خطاطةxml على الإطلاق. على سبيل المثال، يمكنك استخدام variableفي data لوصف متغير يمكن استخدامه في الخطاطة. يمكنك تعريف متغير من نوع Kotlin: <data> <variable name="data" type="org.example.kotlin.databinding.WeatherData"/> </data> يمكنك استخدام الصيغة {}@ لكتابة العبارات، بحيث يمكن الإحالة إلى خصائص Kotlin : <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@{data.imageUrl}" android:contentDescription="@string/image" /> لاحظ أن لغة التعبير عن ربط البيانات تستخدم نفس القواعد للإحالة إلى الخصائص كما هو الحال في Kotlin، أي: data.imageUrl . في Kotlin يمكنك كتابة v.prop بدلا من ()v.getProp حتى لو كانت ()getProp من وظائف جافا. وبالمثل، بدلًا من استدعاء مُحدِّدٍ setter مباشرة، فيمكنك استخدام الإحالة: class MainActivity : AppCompatActivity() { // ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) binding.data = weather // the same as // binding.setData(weather) } } يمكنك ربط مُصغي لإطلاق إجراء ما عندما يحدث حدث معين: <Button android:text="@string/next" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="startOtherActivity" /> هنا startOtherActivity هي وظيفة محددة في النشاط الرئيسي MainActivity: class MainActivity : AppCompatActivity() { // ... fun startOtherActivity(view: View) = startActivity<OtherActivity>() }   يستخدم هذا المثال الدالة startActivity التي تنشئ نيّة intent بدون بيانات وتبدأ نشاطًا جديدًا، والذي يأتي من المكتبة Anko. لأجل تمرير البيانات يمكنك كتابة (startActivity<OtherActivity>("KEY" to "VALUE". لاحظ أنه بدلًا من الإعلان باستخدام lambdas في xml كما في المثال التالي، يمكنك ربط الإجراءات مباشرةً في الكود البرمجي: <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{() -> presenter.onSaveClick(task)}" /> // the same logic written in Kotlin code button.setOnClickListener { presenter.onSaveClick(task) } في السّطر الأخير تم تحديد button بالرّجوع إلى رقم تعريفها id باستخدام ملحقة أندرويد لـ Kotlin. يمكنك استخدام هذه الملحقة كبديل وهو ما سيسمح لك بالحفاظ على منطق الربط في الكود البرمجي والحصول على نص برمجي موجز في نفس الوقت. يمكنك العثور على مشروع تمثيليّ هنا. DBFlow DBFlow هي مكتبةSQLite تسمح بتبسيط التفاعل مع قواعد البيانات. كما تعتمد بشكل كبير على التأشير. لاستخدامها مع Kotlin قم بإعداد ارتباطات التأشير باستخدام kapt: apply plugin: 'kotlin-kapt' dependencies { kapt "com.github.raizlabs.dbflow:dbflow-processor:$dbflow_version" compile "com.github.raizlabs.dbflow:dbflow-core:$dbflow_version" compile "com.github.raizlabs.dbflow:dbflow:$dbflow_version" } إن كان تطبيقك يستخدم بالفعل DBFlow، فيمكنك إقحام Kotlin في المشروع بدون مشاكل. يمكنك التّحويل التّدريجي لأكوادك البرمجية الموجودة إلى Kotlin (لضمان ترجمة كل شيء بشكل متناسق). الكود البرمجي المُحوّل لا يختلف كثيرا عن نظيره في جافا. على سبيل المثال، التصريح بجدول يبدو مشابهًا لجافا مع فارق صغير يتمثّل في أنّ القيم الافتراضية للخصائص يجب تحديدها صراحة: @Table(name="users", database = AppDatabase::class) class User : BaseModel() { @PrimaryKey(autoincrement = true) @Column(name = "id") var id: Long = 0 @Column var name: String? = null } بالإضافة إلى تحويل الوظائف الموجودة إلى Kotlin، يمكنك أيضًا الاستفادة من الدعم الخاص بـ Kotlin. على سبيل المثال، يمكن أن تقوم بتعريف الجداول كـ أصناف بيانات data classes. @Table(database = KotlinDatabase::class) data class User(@PrimaryKey var id: Long = 0, @Column var name: String? = null) يعرّف DBFlow مجموعة من الإضافات لجعل استخدامه في Kotlin أكثر سلاسةً، بحيث يمكنك إدراجه في الارتباطات خاصتك: dependencies { compile "com.github.raizlabs.dbflow:dbflow-kotlinextensions:$dbflow_version" } هذا سيمنحك وسيلة للتعبير عن الاستعلامات بأسلوب مشابه لـ C#/LINQ ، استخدم lambdas لكتابة أكواد أبسط بكثير لأجل العمليّات المتزامنة وغيرها. التّحزيم التلقائي Auto-Parcel يسمح التحزيم التلقائي بتوليد قيم محزومة Parcelable من الأصناف المؤشّرة بـ AutoValue@. عند تحديد الارتباطات dependency استخدم مرةً أخرى kapt كمُؤشّر لمعالجة ملفات Kotlin: apply plugin: 'kotlin-kapt' dependencies { ... kapt "frankiesardo:auto-parcel:$latest-version" } يمكنك العثور على النّموذج المُحوّل من هنا. يمكنك التأشير على أصناف Kotlin بـ AutoValue@. دعونا نلقي نظرة على الصّنف المحوّلAddress الذي سيتمّ توليد صياغته المحزومة Parcelable implementation : @AutoValue abstract class Address : Parcelable { abstract fun coordinates(): DoubleArray abstract fun cityName(): String companion object { fun create(coordinates: DoubleArray, cityName: String): Address { return builder().coordinates(coordinates).cityName(cityName).build() } fun builder(): Builder = `$AutoValue_Address`.Builder() } @AutoValue.Builder interface Builder { fun coordinates(x: DoubleArray): Builder fun cityName(x: String): Builder fun build(): Address } } ليس لـ Kotlin وظائف ثابتة static methods، لذلك ينبغي تضمينها داخل كائن مرافق. إذا كنت مصرًّا على استخدامها من داخل الأكواد البرمجية لجافا، قم بالتأشير عليهم بـ JvmStatic. إن كنت ترغب في الوصول إلى صنف أو وظيفة من جافا باسم غير صالح في Kotlin، يمكنك تمرير الاسم داخل علامتي تنصيص (``)، كما هو الحال مع الصّنف التي تم إنشاؤه AutoValue_Address$. عمومًا الكود البرمجي المُحوّل يبدو مشابها كثيرا لكود جافا الأصلي. ترجمة -وبتصرّف- للمقال Android Frameworks Using Annotation Processing من توثيقيات KOTLIN هذا المقال منشور تحت رخصة Apache 2 license
  8. في هذا الدرس، ستتعلم كيفية استخدام خُطّافات (Git (Git hooks لأتمتة نشر بيئة الإنتاج لتطبيقات Rails على خادم أوبونتو 14.04 عن بُعد. باستخدام خُطّافات Git ستتمكن من نشر التطبيقات عن طريق دفع التغييرات إلى خادم الإنتاج production server، وبدلًا من أن تقوم بكل شيء يدويًّا (مثل ترحيل قاعدة البيانات) فالاستعانة بأحد أشكال النشر الآلي، مثل خُطّافات Git، سيوفر عليك الكثير من الوقت على المدى الطويل. في هذا الدرس سنستخدم خُطّافGit من نوعpost-receive ، بالإضافة إلىPuma كخادم للتطبيق،Nginx كوكيل عكسي لـ Puma و PostgreSQL كقاعدة بيانات. المتطلبات الأساسية سوف تحتاج صلاحيات مستخدم غير جذري non-root والذي يملك امتيازات مستخدم أساسي superuser على خادم أوبونتو. في هذا المثال، سيكون اسم المستخدم deploy. يمكنك تعلم كيفية فعل ذلك في هذا الدرس: الإعداد الابتدائي لخادوم أوبنتو 14.04. إذا كنت ترغب في النشر دون الحاجة لإدخال كلمة المرور، فتأكد من إعداد مفاتيح SSH. سوف تحتاج إلى تثبيت Ruby على خادمك. إذا لم تكن قد فعلت ذلك سلفًا، يمكنك تثبيته جنبًا إلى جنب مع Rails باستخدام rbenv أو RVM. سوف تحتاج أيضًا إلى تطبيق Rails مُدار في مستودع git على جهازك. إذا لم يكن لديك تطبيق في git، فسوف نقدم لك تطبيقًا بسيطًا كمثال لتعمل عليه. لنبدأ على بركة الله. تثبيت PostgreSQL معظم بيئات Rails تستخدم PostgreSQL كقاعدة بيانات، لذلك عليك تثبيته على خادمك الآن. على خادم الإنتاج، قم بتحديث apt-get: sudo apt-get update ثم قم بتثبيت PostgreSQL بهذه التعليمات: sudo apt-get install postgresql postgresql-contrib libpq-dev إنشاء قاعدة بيانات الإنتاج الخاصة بالمستخدم لإبقاء الأمور بسيطةً، سنسمي قاعدة بيانات الإنتاج الخاصة بالمستخدم بنفس اسم التطبيق خاصتك. على سبيل المثال، إذا كان اسم تطبيقك “appname”، فيجب عليك إنشاء مستخدم PostgreSQL بهذه الطريقة: sudo -u postgres createuser -s appname لتعيين كلمة مرور لقاعدة بيانات المستخدم، ادخُل سطر أوامر PostgreSQL هكذا: sudo -u postgres psql بعد ذلك قم بتعيين كلمة المرور لقاعدة بيانات المستخدم “appname” هكذا: \password appname قم بإدخال كلمة المرور التي تريد ثم قم بتأكيدها. اخرج من سطر أوامر PostgreSQL بهذه التعليمة: \q الآن نحن على استعداد لتزويد تطبيقك بمعلومات الاتصال الخاصة بقاعدة البيانات. إعداد تطبيق Rails على جهاز التطوير خاصتك، ستقوم بإعداد تطبيقك لأجل النشر. اختياري: إنشاء تطبيق Rails إن كان لديك تطبيق Rails جاهز للنشر. فيمكنك تخطي هذا القسم والقيام بالتغييرات المناسبة لاحقًا. أمّا إن لم يكن لديك تطبيق جاهز، فإن الخطوة الأولى هي إنشاء تطبيق Rails جديدة. هذه التعليمات ستنشئ تطبيق Rails جديد تحت اسم “appname” في المجلد الرئيسي. لا تتردد في استبدال “appname” بالاسم الذي تريد: cd ~ rails new appname ثم تحوّل إلى مجلد التطبيق: cd appname لأجل تطبيقنا هذا، سوف نقوم بتوليد سقالة scaffold controller لكي يجد تطبيقنا شيءً ليعرضه: rails generate scaffold Task title:string note:text لنتأكدْ الآن من أن تطبيقنا موجود في مستودعgit . تهيئة Git Repo إن لم يكن تطبيقك موجودًا بالفعل في مستودع git لسبب ما، قم بتهيئته وإجراء إلزام أولي initial commit. قم بالتحوّل إلى مجلد التطبيق. في مثالنا، التطبيق يسمى " appname" وهو موضوع في المجلد الرئيسي home directory: cd ~/appname git init git add -A git commit -m 'initial commit' الآن دعونا نُجهّز تطبيقنا لربط الاتصال بقاعدة بيانات الإنتاج لـ PostgreSQL. تحديث إعدادات قاعدة البيانات تحوّل إلى مجلد تطبيقك إن لم تكن بالفعل هناك. في مثالنا، التطبيق يسمى “appname” وهو موضوع في المجلد الرئيسي home directory: cd ~/appname الآن افتح ملف إعداد قاعدة البيانات في محرر النصوص المفضل لديك: vi config/database.yml اعثر على مقطع الإنتاج production section في إعدادات قاعدة بيانات تطبيقك، وقم باستبداله بمعلومات الاتصال بقاعدة بيانات الإنتاج خاصتك. من المفروض أن يبدو كشيء من هذا القبيل (قم باستبدال القيم عند الاقتضاء): config/database.yml excerpt production: <<: *default host: localhost adapter: postgresql encoding: utf8 database: appname_production pool: 5 username: <%= ENV['APPNAME_DATABASE_USER'] %> password: <%= ENV['APPNAME_DATABASE_PASSWORD'] %> احفظ واخرج. هذا الملف يؤكد على أن بيئة الإنتاج الخاصة بالتطبيق ينبغي أن تستخدم قاعدة بيانات PostgreSQL تحت مُسمّى “appname_production” على المضيف المحلي localhost. لاحظ أنه تم إحالة اسم المستخدم وكلمة مرور قاعدة البيانات إلى متغيرات البيئة environment variables. سنقوم بتحديدها على الخادم في وقت لاحق. تحديث Gemfile إذا لم يكن لدى Gemfile خاصتك المكتبة pg (PostgreSQL adapter gem)، ولم تكن المكتبة Puma مُحددة، فيجب عليك إضافتهما الآن. افتح Gemfile الخاص بتطبيقك في المحرّر المفضل لديك: vi Gemfile أضف الأسطر التالية إلىGemfile : Gemfile excerpt group :production do gem 'pg' gem 'puma' end احفظ واخرج. سيحدد هذا النص البرمجي أن بيئة الإنتاج production environment يجب أن تستخدم المكتبات pgوpuma : إعداد Puma قبل إعداد Puma، يجب عليك أن تتحقق من عدد وحدات المعالجة المركزية التي يملكها خادمك. يمكنك بسهولة فعل ذلك على خادمك بهذه التعليمة: grep -c processor /proc/cpuinfo الآن، على جهاز التطوير خاصتك، قم بإضافة إعدادات Puma إلى الإعداد config/puma.rb . افتح الملف في محرر النصوص: vi config/puma.rb انسخ وألصق هذه الإعدادات في الملف: config/puma.rb # Change to match your CPU core count workers 2 # Min and Max threads per worker threads 1, 6 app_dir = File.expand_path("../..", __FILE__) shared_dir = "#{app_dir}/shared" # Default to production rails_env = ENV['RAILS_ENV'] || "production" environment rails_env # Set up socket location bind "unix://#{shared_dir}/sockets/puma.sock" # Logging stdout_redirect "#{shared_dir}/log/puma.stdout.log", "#{shared_dir}/log/puma.stderr.log", true # Set master PID and state locations pidfile "#{shared_dir}/pids/puma.pid" state_path "#{shared_dir}/pids/puma.state" activate_control_app on_worker_boot do require "active_record" ActiveRecord::Base.connection.disconnect! rescue Ac-tiveRecord::ConnectionNotEstablished Ac-tiveRecord::Base.establish_connection(YAML.load_file("#{app_dir}/config/database.yml")[rails_env]) end قم بتغيير العددworkers إلى عدد وحدات المعالجة المركزية لخادمك. يفترض المثال أن لديك اثنان. احفظ واخرج. الآن تم إعداد Puma بموضعlocation تطبيقك وموضع مقبسه socket والمذكرات logs ومعرّفات العمليات PIDS. لا تتردد في تعديل الملف، أو إضافة الخيارات التي تناسبك. ألزمCommit التغييرات الأخيرة: git add -A git commit -m 'added pg and puma' قبل الاستمرار، قم بتوليد المفتاح السري والذي سيتم استخدامه لبيئة الإنتاج الخاصة بتطبيقك: rake secret rake secret sample output: 29cc5419f6b0ee6b03b717392c28f5869eff0d136d8ae388c68424c6e5dbe52c1afea8fbec305b057f4b071db1646473c1f9a62f803ab8386456ad3b29b14b89 سوف تنسخ المُخرجات وتستخدمها لتحديد القيمة SECRET_KEY_BASE الخاصة بتطبيقك في الخطوة التالية. إنشاء النص البرمجي لإطلاق Puma سنقوم بإنشاء نص برمجي للإطلاق (Upstart init script). حتى نتمكن من تشغيل وإيقاف Puma بسهولة، وللتأكد من أنه سيبدأ عند بدء التشغيل. على خادم الإنتاج خاصتك، حمّل أداة Jungle Upstart من مستودع Puma على GitHub وضعها في المجلد الرئيسي: cd ~ wget https://raw.githubusercontent.com/puma/puma/master/tools/jungle/upstart/puma-manager.conf wget https://raw.githubusercontent.com/puma/puma/master/tools/jungle/upstart/puma.conf الآن افتح الملف puma.conf حتى تتمكن من تحرير إعدادات النشر الخاصة بمستخدمPuma : vi puma.conf ابحث عن السطرين الذين يحددان setuid و setgid، و قم باستبدال “apps” باسم النشر الخاص بالمستخدم أو المجموعة خاصتك. على سبيل المثال، إذا كان اسم مستخدم النشر “deploy”، فينبغي أن تكون الأسطر هكذا: puma.conf excerpt 1 of 2 setuid deploy setgid deploy الآن ابحث عن السطر الذي يحتوي:exec /bin/bash <<'EOT'. أضف الأسطر التالية تحته، وتأكد من استبدال اسم المستخدم وكلمة المرور الخاصة ب PostgreSQL، وأضف كذلك rake secret الذي قمت بإنشائه سابقًا: puma.conf excerpt 2 of 2 export APPNAME_DATABASE_USER='appname' export APPNAME_DATABASE_PASSWORD='appname_password' export SECRET_KEY_BASE='rake_secret_generated_above' احفظ واخرج. الآن انسخ النصوص في مجلد خدمات الإطلاق Upstart services: sudo cp puma.conf puma-manager.conf /etc/init النص البرمجي puma-manager.conf يُحدد /etc/puma.conf كمرجع لمعرفة التطبيقات التي يجب إدارتها. دعونا ننشئ ونحرّر هذا الملف الآن: sudo vi /etc/puma.conf كل أسطر هذا الملف يجب أن تتضمن مسارات التطبيقات التي تريد من Puma أن يُديرها. سنقوم بنشر تطبيقنا في مجلد يُسمى “appname” داخل المجلد الرئيسي. في هذا المثال، سيكون كما يلي (تأكد من تعديل المسار ليتناسب مع المكان الذي يتواجد فيه تطبيقك): /etc/puma.conf /home/deploy/appname احفظ واخرج الآن تمّ إعداد تطبيقك لينطلق عند بدء التشغيل بمساعدة Upstart, وهذا يعني أن تطبيقك سيبدأ حتى بعد إعادة إقلاع خادمك. لا تنسى أننا لم ننشر التطبيق حتى الآن، لذلك لسنا جاهزين لتشغيله بعد. تثبيت وإعداد Nginx لجعل التطبيق متاحًا على شبكة الإنترنت، يجب أن تستخدم Nginx كخادم. قم بتثبيت Nginx باستخدام apt-get: sudo apt-get install nginx الآن افتح كتلة الخادم الافتراضي default server block بمحرر النصوص: sudo vi /etc/nginx/sites-available/default استبدل محتويات الملف بالتعليمات البرمجية التالية. تأكد من استبدال الأجزاء الملوّنة باسم المستخدم واسم التطبيق المناسبين. /etc/nginx/sites-available/default upstream app { # Path to Puma SOCK file, as defined previously server unix:/home/deploy/appname/shared/sockets/puma.sock fail_timeout=0; } server { listen 80; server_name localhost; root /home/deploy/appname/public; try_files $uri/index.html $uri @app; location @app { proxy_pass http://app; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; } error_page 500 502 503 504 /500.html; client_max_body_size 4G; keepalive_timeout 10; } احفظ واخرج. سيقوم هذا النص البرمجي بإعداد Nginx كوكيل عكسي، لذلك طلبات HTTP ستُرسل إلى الخادم Puma عبر مقبس يونيكس Unix socket. لا تتردد في إجراء التغييرات التي تراها مناسبةً. لن نقوم بإعادة تشغيل Nginx, فالتطبيق غير موجود بعدُ على الخادم. سنقوم بإعداد التطبيق فيما يلي. إعداد مستودع الإنتاج (git (Prepare Production Git Remote على خادم الإنتاج، قم بتثبيت git بواسطة apt-get: sudo apt-get install git ثم قم بإنشاء مجلد للمستودع البعيد remote repository. سنقوم بإنشاء مجلد git أوّلي في المجلد الرئيسي وسنسميه “appname_production”. يمكنك تسمية المستودع البعيد كما تريد (ولكن لا تضعه في ~/appnameلأنه المكان الذي سننشر فيه التطبيق): mkdir ~/appname_production cd ~/appname_production git init –bare بما أن هذا المستودع أوّلي، فلا يوجد مجلّد عمل بعدُ وجميع الملفات الموجودة في .git موجودة في المجلد الرئيسي نفسه. نحن بحاجة إلى إنشاء خُطّاف git من نوعpost-receive ، والذي هو النص البرمجي الذي سيتم تشغيله عندما يتلقى خادم الإنتاج دفعةً من git(git push). افتح الملف hooks/post-receive في محرر النصوص: vi hooks/post-receive انسخ وألصق النص التالي في الملف post-receive: hooks/post-receive #!/bin/bash GIT_DIR=/home/deploy/appname_production WORK_TREE=/home/deploy/appname export APPNAME_DATABASE_USER='appname' export APPNAME_DATABASE_PASSWORD='appname_password' export RAILS_ENV=production . ~/.bash_profile while read oldrev newrev ref do if [[ $ref =~ .*/master$ ]]; then echo "Master ref received. Deploying master branch to produc-tion..." mkdir -p $WORK_TREE git --work-tree=$WORK_TREE --git-dir=$GIT_DIR checkout -f mkdir -p $WORK_TREE/shared/pids $WORK_TREE/shared/sockets $WORK_TREE/shared/log # start deploy tasks cd $WORK_TREE bundle install rake db:create rake db:migrate rake assets:precompile sudo restart puma-manager sudo service nginx restart # end deploy tasks echo "Git hooks deploy complete" else echo "Ref $ref successfully received. Doing nothing: only the mas-ter branch may be deployed on this server." fi done تأكد من تحديث القيم التالية: GIT_DIR :مجلد المستودع الأولي لـ (git (bare git repository الذي قمت بإنشائه في وقت سابق WORK_TREE : المجلد حيث تريد نشر تطبيقك (يجب أن يتطابق مع الموضع الذي قمت بتحديده في إعدادات Puma) APPNAME_DATABASE_USER :اسم مستخدم PostgreSQL (ضروري لمهام rake ) APPNAME_DATABASE_PASSWORD : كلمة مرور PostgreSQL (ضروري لمهام rake ) بعد ذلك، يجب عليك مراجعة التعليمات الموجودة بين التعليقين # start deploy tasks و # end deploy tasks. هذه هي التعليمات التي سيتم تشغيلها في كل مرة يتم دفع push الشعبة الرئيسية master branch إلى مستودع الإنتاج في(git (appname_production. إذا تركتها كما هي، فسيحاول الخادم القيام بما يلي بالنسبة لبيئة الإنتاج الخاصة بتطبيقك: تشغيل المُحزّم bundler إنشاء قاعدة بيانات ترحيل قاعدة البيانات الترجمة الأوليةPrecompile للأصول assets إعادة تشغيل Puma إعادة تشغيل Nginx إذا كنت ترغب في إجراء أية تغييرات أو أي إضافات للتحقق من الأخطاء، لا تتردد في القيام بذلك. بمجرد الانتهاء من مراجعة النص البرمجي احفظه واخرج. بعد ذلك، اجعل البرنامج النصي قابلًا للتنفيذ: chmod +x hooks/post-receive Sudo بلا كلمة مرور Passwordless Sudo لأن الخُطّاف post-receive يحتاج إلى تشغيل تعليماتsudo ، فسنسمح للمستخدم deploy باستخدام sudo بدون كلمة مرور(استبدل اسم المستخدمdeploy في حال اخترت اسمًا مختلفًا): sudo sh -c 'echo "deploy ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/90-deploy' هذا سيسمح للمستخدم deploy بتشغيل التعليمة sudo دون الحاجة لإعطاء كلمة المرور. ربما تريد أن تُقيّد التعليمات التي يمكن للمستخدمdeploy القيام بها. وكحد أدنى، عليك استخدام مفتاح المصادقة SSH كما عليك تعطيل المصادقة بكلمة المرور password authentication. إضافة Production Git Remote الآن بعد أن أعددنا كل شيء لخادم الإنتاج، دعونا نضيف production git remote لمستودع التطبيق خاصتنا. على جهاز التطوير خاصتك، تأكد من أنك في مجلد التطبيق: cd ~/appname ثم قم بإضافة مستودع git بعيد (git remote) جديد تحت اسم “production” والذي يشير إلى مستودع git الأولي appname_production الذي أنشأته على خادم الإنتاج. استبدل اسم المستخدم (deploy) وعنوان الـ IP الخاص بالخادم واسم المستودع البعيد (appname_production): git remote add production deploy@production_server_public_IP:appname_production لقد صار تطبيقك الآن جاهزًا للنشر بواسطة git push. النشر للإنتاج Deploy to Production بعد كل الإعدادات التي قمنا بها، يمكنك الآن نشر تطبيقك على الخادم خاصتك عن طريق تشغيل تعليمات git التالية: git push production master هذا سيدفع push شعبتك الرئيسية المحلية local master branch إلى مستودع الإنتاج البعيد production remote الذي قمت بإنشائه سابقًا. عندما يتلقى production remote أمر الدفع، فسينفّذ النصَّ البرمجي post-receive الذي أعددناه في وقت سابق. إذا قمت بكل شيء بشكل صحيح، فيجب أن يكون تطبيقك متاحًا الآن على عنوان الـ IP العام لخادم الإنتاج خاصتك. إذا كنت تستخدم التطبيق التعليمي لهذا الدرس، فمن المفروض أن تكون قادرًا على الوصول إلى http://production_server_IP/tasks من أيّ متصفح و من المفروض أن ترى شيئًا من هذا القبيل: الخلاصة في أي وقت تقوم بإجراء تغيير على تطبيقك، يمكنك تشغيل نفس التعليمة git push للنشر على خادم الإنتاج خاصتك. هذا لوحده من المفروض أن يوفر عليك الكثير من الوقت على مدى عمر المشروع. لقد شمل هذا الدرس فقط الخطّافات من نوع “post-receive”، ولكن هناك عدة أنواع أخرى من الخطّافات التي يمكن أن تساعدك على تحسين أتمتة عملية النشر. ترجمة -وبتصرّف- للمقال How To Deploy a Rails App with Git Hooks on Ubuntu 14.04 لصاحبه Mitchell Anicas
  9. تم تصميم خطاطة CSS الشّبكيّة لتعمل هي وباقي أجزاء CSS كجزء من نظام شامل لإنجاز التخطيط layout. في هذا الدّرس سنشرح كيف أنّ الشّبكة grid تتّسق مع التقنيات الأخرى التي ربّما تستخدمها سلفًا. Grid و flexbox الفرق الأساسي بين خطاطة CSS الشّبكيّة وخطاطة CSS flexbox هي أنّ flexbox تمّ تصميمه لأجل الخطاطات أحادية البعد، بمعنى خطاطة يُمكن أن تُسلك في صفّ أو عمود. أمّا الشّبكةGrid فقد تمّ تصميمها لأجل الخطاطات ثنائيّة الأبعاد، بمعنى خطاطة تَسري في الصّفوف والأعمدة في نفس الوقت. كلا المواصفتين القياسيتينspecifications تتشاركان في بعض السّمات، وإن كنت قد تعلّمت من قبل كيفيّة استخدام flexbox فسترى بعض أوجه التشابه التي ستساعدك في تعلّم مفهوم الشّبكة بسرعة. الخطاطة أحاديّة البعد vs الخطاطة ثنائية البعد سنستخدم مثال بسيط يمكن أن يُوضّح الفرق بين الخطاطة أحادية البعد أو ثنائية الأبعاد. في المثال الأول، سنستخدم flexbox لنَظم مجموعة من المربّعات. سيكون لدينا خمسة أبناء في الوعاء container، وسنُعيّن للخاصيّةflex قيمًا بحيث يمكنها أن تنبسط وتنقبض في حدود هامش 200 بكسل. كما سنحدّد للخاصّية flex-wrap القيمةwrap ، بحيث إذا صارت المساحة في الوعاء أضيق من أن تستوعب المرونة الأساسيّة flex basis، فسيتمّ وضع العناصر في صفّ جديد. <div class="wrapper"> <div>One</div> <div>Two</div> <div>Three</div> <div>Four</div> <div>Five</div> </div> .wrapper { display: flex; flex-wrap: wrap; } .wrapper > div { flex: 1 1 200px; } يمكنك أن ترى في الصورة أن اثنين من العناصر انتظما في سطر جديد. هذان العنصران يتقاسمان الحيّز المتاح ولا يصطفّان تحت العناصر الموجودة أعلاها. يحدث هذا لأنّه عندما تُغلّف العناصر المرنة flex items، فإنّ كل صف جديد (أو عمود عند العمل بالأعمدة) يصبح وعاءً مرنًا جديدًا. حيث أنّ توزيع المساحة يحدث على طول الصف. أحد الأسئلة الشائعة هو كيفية جعل هذه العناصر تصطفّ. هنا نحتاج استخدام الخطاطة ثنائيّة الأبعاد: لأنّك إن كنت تريد ضبط المحاذاة عبر الصفوف والأعمدة، فستحتاج إلى الشّبكة grid . نفس الخطاطة لكن مع شبكات CSS في هذا المثال، سنُنشئ نفس الخطاطة باستخدام الشّبكة. هذه المرة لدينا ثلاثة مدارج للأعمدة column tracks. لسنا بحاجة لوضع أي شيء للعناصر نفسها. حيث سينتظمون تلقائيا واحد في كل خلية من الشّبكة التي تم إنشاؤها. وكما هو واضح فقد بقيت في شبكة صارمة strict grid، حيث يصطفّون في صفوف وأعمدة. ومع وجود خمسة عناصر، فستكون هناك فجوة في نهاية الصف 2. <div class="wrapper"> <div>One</div> <div>Two</div> <div>Three</div> <div>Four</div> <div>Five</div> </div> .wrapper { display: grid; grid-template-columns: repeat(3، 1fr); } سؤال بسيط يجب أن تسأله عند المفاضلة بين الشّبكة و flexbox وهو: هل أنا بحاجة فقط لضبط الخطاطة على صعيد الصّفوف وحدها أو الأعمدة وحدها – استخدم إذًا flexbox هل أحتاج لضبط الخطاطة على صعيد الصفوف والأعمدة معًا – استخدم إذًا الشّبكة محتوى خارجيّ أم خطاطة داخليّة؟ بالإضافة إلى مسألة التفريق بين البعد الواحد والأبعاد الثّنائية، هناك طريقة أخرى لتقرّر ما إذا كان عليك استخدام flexbox أو الشّبكة لأجل التخطيط. يتكيّف flexbox مع حجم المحتوى. فمن الحالات المثالية لاستخدام flexbox هي عندما يكون لديك مجموعة من العناصر التي تريد توزيعها بالتساوي في الوعاء. بحيث يتمّ تحديد المساحة التي سيشغلها كل عنصر على حسب حجم المحتوى. وإذا انتظمت العناصر في سطر جديد، فستتوزع على أساس أحجامها والمساحة المتاحة في ذلك السّطر. الشّبكة تعمل في الاتجاه الداخلي للخطاطة layout in، فعند استخدام خطاطة CSS الشّبكيّة يتمّ إنشاء خطاطة ثم توضع العناصر فيها، وفي حال استخدام قواعد auto-placement ستوضع العناصر في خلايا الشّبكة وفقا للشّبكة الصارمة strict grid. هناك إمكانيّة لإنشاء مدارج tracks تتكيّف مع حجم المحتوى، ولكنّها ستغيّر أيضا كامل المدرج. إذا كنت تستخدم flexbox وتشعر أنّك تفتقد بعض المرونة، فربّما عليك استخدام خطاطة CSS الشّبكيّة. على سبيل المثال إذا حدّدت عرض width عنصر مرن (flex item) بنسبة مئوية لجعله يصطف مع العناصر الأخرى في الصّف الأعلى. في هذه الحالة، فمن المحتمل أن يكون استخدام الشّبكة خيارًا أفضل لك. محاذاة المربّع Box alignment لعلّ من أكثر سمات flexbox إثارة هي أنه مكّننا لأول مرة من التحكم في المحاذاة بشكل صحيح. فقد جعل من السهل توسيط مربع على الصفحة. ويمكن للعناصر المرنة الامتداد على كامل ارتفاع الوعاء، وهذا يعني أنّه صار من الممكن الحصول على أعمدة متساوية الارتفاع. وهي أشياء كنّا نريد القيام به منذ فترة طويلة جدا، كما أنها جاءت مرفقة بجميع أنواع الحيل لمساعدتنا على خلق التأثير البصري الذي نريد. خصائص المحاذاة المحدّدة في المواصفات القياسيّة specification لـ flexbox قد تمّ إضافتها إلى مواصفات قياسيّة جديدة تسمّى Box Alignment Level 3 . وهذا يعني أنّه يمكن استخدامها في مواصفات قياسيّة أخرى، بما في ذلك الخُطاطة الشّبكيّة. وفي المستقبل، قد تنطبق أيضًا على أساليب التخطيط الأخرى. إليك الآن مثالًا بسيطًا لمقارنة flexbox والشّبكة. في المثال الأول، والذي يستخدم flexbox، لدينا وعاء يحتوي ثلاثة عناصر. تمّ تعيين خاصيّة min-height للغلافwrapper ، لتحديد ارتفاع الوعاء المرن flex container. سنعطي أيضا للخاصيّة align-items الخاصّة بالوعاء المرن القيمة flex-end، وبالتالي فإن العناصر سوف تصطفّ في نهاية الوعاء. كما أنّنا أيضا سنحدد الخاصية align-self لـ box1 حتّى تستبدل السّلوك الافتراضي وتمتد على كامل ارتفاع الوعاء وعلى box2 حتّى يصطفّ في بداية الوعاء المرن. <div class="wrapper"> <div class="box1">One</div> <div class="box2">Two</div> <div class="box3">Three</div> </div> .wrapper { display: flex; align-items: flex-end; min-height: 200px; } .box1 { align-self: stretch; } .box2 { align-self: flex-start; } المحاذاة في شبكات CSS يستخدم المثال الثاني الشّبكة لإنشاء نفس التّصميم. هذه المرة سنستخدم خصائص مربع المحاذاة التي تنطبق على الخطاطة الشبكيّة. لذلك سنستخدم start وend لأجل المحاذاة بدلًا من flex-start وflex-end. في حالة الخطاطة الشبكيّة، فنحن نحاذي العناصر داخل الحيّز الخاص بهم الشّبكة. وهو في هذه الحالة خليّة واحدة، ولكن لا شيء يمنع أن يكون الحيّز مكوّنًا من عدة خلايا. <div class="wrapper"> <div class="box1">One</div> <div class="box2">Two</div> <div class="box3">Three</div> </div> .wrapper { display: grid; grid-template-columns: repeat(3،1fr); align-items: end; grid-auto-rows: 200px; } .box1 { align-self: stretch; } .box2 { align-self: start; } الوحدة fr و flex-basis لقد رأينا سابقا كيف نستخدم الوحدةfr لأجل تخصيص نسبة من المساحة المتوفرة في وعاء الشّبكة لأجل مدرج الشّبكة grid tracks. عندما تُستخدم الوحدة fr مع الدالّة ()minmax فيمكن أن تعطينا سلوكًا مشابهًا جدًا للخاصيةflex في flexbox وفي الوقت نفسه تُمكّننا من إنشاء خطاطة في بعدين. لو عدنا إلى المثال الذي أوضحنا فيه الفرق بين الخطاطات أحادية وثنائيّة الأبعاد، فيمكنك أن ترى أن هناك فرقا في الطريقة التي تتكيّف وتتجاوب بها الخطاطتان. فمع الخطاطة المرنة، إذا غيّرنا حجم النافذة توسيعًا وتصغيرًا، سيتكيّف flexbox بشكل جيد بتعديل عدد العناصر في كل صف وفقا للمساحة المتاحة. إن كان لدينا ما يكفي من المساحة فكل العناصر الخمسة ستنتظم في صف واحد، أمّا إن كان الوعاء ضيّقًا فقد لا نجد مساحة لأكثر من واحد. على سبيل المقارنة، النسخة الشبكيّة دائما ما يكون لديها ثلاثة مدارج للأعمدة column tracks. والمدارج نفسها سوف تنبسط وتنقبض، ولكن ستكون هناك دائما ثلاثة أعمدة كما طلبنا عند تعريف الشّبكة. الملء التلقائي لمدارج الشّبكة Auto-filling grid tracks يمكننا خلق تأثير مماثل لـ flexbox، دون التفريط في إمكانيّة الحفاظ على المحتوى مرتبًا في صفوف وأعمدة صارمة، من خلال إنشاء تسلسل المدارج track listing باستخدام العبارةrepeat وخاصّيتي auto-fill و auto-fit. في المثال التالي، سنستخدم auto-fill بدلًا من استخدام الأعداد الصحيحة في العبارة repeat وسنحدّد تسلسل المدارج track listing بـ 200 بكسل. وهذا يعني أن الشّبكة سوف تنشئ أكبر عدد من مدارج الأعمدة ذات 200 بكسل التي يمكن احتواؤها في الوعاء. <div class="wrapper"> <div>One</div> <div>Two</div> <div>Three</div> </div> .wrapper { display: grid; grid-template-columns: repeat(auto-fill، 200px); } عدد مرن من المدارج الأمر هنا مختلف بعض الشيء عن flexbox. ففي مثال flexbox، كانت العناصر أكبر من 200 بكسل قبل التغليف wrapping. يمكننا تحقيق نفس الشّيء في الشّبكة من خلال الجمع بين auto-fill و ()minmax . في المثال التالي، سنُنشئ مدارج تُملأ تلقائيا بواسطة minmax. هدفنا أن نجعل الحدّ الأدنى لحجم المدارج لا يقلّ عن 200 بكسل، سنعيّن الحد الأقصى عند1fr . وبمجرّد أن يخمّن المتصفح كم “200 بكسل” سوف يتّسع لها الوعاء، مع احتساب فجوات الشبكة، فسوف يعتبر الحدّ الأقصى المحدّد 1fr كتعليمات لتوزيع المساحة المتبقية بين العناصر. <div class="wrapper"> <div>One</div> <div>Two</div> <div>Three</div> </div> .wrapper { display: grid; grid-template-columns: repeat(auto-fill، minmax(200px، 1fr)); } لدينا الآن القدرة على إنشاء شبكة بعدد مرن من المدارج المرنة (flexible number of flexible tracks)، ولكن مع محاذاة العناصر التي وُضعت في الشّبكة في الصفوف والأعمدة. الشّبكة والعناصر مُطلقة التموضِع Grid and absolutely positioned elements تتفاعل الشّبكة مع العناصر مطلقة التّموضع، وهو أمر يمكن أن يكون مفيدًا إذا كنّا نرغب في وضع عنصر داخل شبكة أو حيّز من الشبكة. تحدد المواصفات القياسيّة السلوك الافتراضي عندما يكون وعاء الشّبكة كتلة احتواء containing block وحيث يكون وعاء الشّبكة أبًا للعنصر مطلق التّمَوضُع absolutely positioned. وعاء الشبكة ككتلة احتواء لجعل وعاء الشّبكة كتلة احتواء نحتاج إلى إعطاء القيمة relative للخاصيةposition الخاصّة بالوعاء، بالضّبط كما تفعل عادة لصنع كتلة احتواء لأيّ من العناصر الأخرى مُطلقة التموضع absolutely positioned. بعد فعل ذلك، إن حدّدنا لعنصر من الِشبكة position: absolute فإنه سيأخذ نفس الحجم الذي تأخذه كتلة الاحتواء الخاصّة بوعاء الشّبكة، أمّا في حال كان لهذا العنصر الخاصيّة grid position، فسيأخذ حيّز الشّبكة التي وُضع فيها. في المثال أدناه لدينا غلاف يحتوي أربعة عناصر. العنصر الثالث مطلق التموضع وأيضا موضوع على الشّبكة باستخدامline-based placement. وعاء الشّبكة لديه position: relative وكذلك سياق التموضع positioning context لذلك العنصر. <div class="wrapper"> <div class="box1">One</div> <div class="box2">Two</div> <div class="box3"> This block is absolutely positioned. In this example the grid container is the containing block and so the absolute positioning offset values are calculated in from the outer edges of the area it has been placed into. </div> <div class="box4">Four</div> </div> .wrapper { display: grid; grid-template-columns: repeat(4،1fr); grid-auto-rows: 200px; grid-gap: 20px; position: relative; } .box3 { grid-column-start: 2; grid-column-end: 4; grid-row-start: 1; grid-row-end: 3; position: absolute; top: 40px; left: 40px; } يمكنك أن ترى أن العنصر قد أخذ حيّزًا من الصّف الثاني وحتى الرابع، ثمّ بدأ بعد السّطر 1. مع تحديد احداثيّته باستخدام الخصائصtop و left. كما تمّ إخراجه من الانسيابflow كما هي عادة العناصر مطلقة التموضع كما تضعهم قواعدautoplacement في نفس المساحة. كما لم يتمّ إنشاء صفّ جديد حتّى يمتد العنصر إلى سطر الصّف 3. إن أزلت position: absolute من قواعد .box3 يمكنك أن ترى كيف سيُعرض بدون تحديد الموضع. وعاء الشّبكة كأبٍ A grid container as parent إذا كان لدى الابن مُطلَق التّموضع وعاء شبكيّ grid container كأبٍ ولكنّ ذلك الوعاء لا يخلق سياق تموضع positioning context جديد، فهذا يعني أنه قد أُخرج من الانسيابflow كما في المثال السابق. سياق التموضع positioning context سيكون كالسياق الذي أنشأه العنصر كما هو شائع في أساليب التخطيط الأخرى. في حالتنا، إذا أزلنا position: relative من الغلاف أعلاه، سياق التموضع سيكون من viewport ، كما هو موضح في هذه الصورة. لم يعد يشارك هذا العنصر مرّة أخرى في الخطاطة الشّبكيّة من حيث التحجيم أو عندما تحدّد مواضع العناصر الأخرى بشكل تلقائي. مع حيّز الشّبكة كأب With a grid area as the parent إذا وٌضع العنصر مطلق التموضع داخل حيّز الشّبكة فيمكنك حينها إنشاء سياق تموضع على الحيّز. في المثال أدناه لدينا شبكة كما قبل ولكن هذه المرة سنُدرج عنصرا داخل .box3 في الشّبكة. سنعيَن القيمةrelative للخاصيّةposition الخاصة بـ .box3 ثم سنحدّد موضع العنصر الفرعي بواسطة خصائص الإزاحة offset properties. في هذه الحالة، سياق التّموضع سيكون هو حيّز الشّبكة. <div class="wrapper"> <div class="box1">One</div> <div class="box2">Two</div> <div class="box3">Three <div class="abspos"> This block is absolutely positioned. In this example the grid area is the containing block and so the absolute positioning offset values are calculated in from the outer edges of the grid area. </div> </div> <div class="box4">Four</div> </div> .wrapper { display: grid; grid-template-columns: repeat(4،1fr); grid-auto-rows: 200px; grid-gap: 20px; } .box3 { grid-column-start: 2; grid-column-end: 4; grid-row-start: 1; grid-row-end: 3; position: relative; } .abspos { position: absolute; top: 40px; left: 40px; background-color: rgba(255،255،255،.5); border: 1px solid rgba(0،0،0،0.5); color: #000; padding: 10px; } الشّبكة و display: contents سنقوم بإطلالة أخيرة على إحدى المواصفات القياسيّة للتّخطيط والتي تستحقّ التنويه وهي التفاعل بين خطاطة CSS الشّبكيّة وdisplay: contents. قيمةcontents للخاصيةdisplay هي قيمة جديدة موضّحة في المواصفات القياسيّة (Display specification ) كما يلي: "إنّ العنصر نفسه لا يولّد أية مربّعات، ولكنّ بإمكان أبنائه وأشباه العناصر pseudo-elements توليد المربّعات كالمعتاد. ولغرض توليد المربعات والتخطيط، يجب أن يعامل العنصر كما لو أنه قد تم استبداله بأبنائه وبأشباه العناصر في شجرة وثيقة" إذا حدّدت لعنصر ما display: contents فإنّ المربع الذي سينشئه سيختفي، أمّا مربّعات الأبناء فستظهر كما لو أنها قد ارتفعت لمستوى أعلى. وهذا يعني أن أبناء عناصر الشّبكة يمكن أن يصبحوا بدورهم عناصر للشّبكة. إن بدا هذا غريبا فإليك مثالًا بسيطًا. في الوسوم التالية، لدينا شبكة بحيث سيشمل أوّل عناصرها جميع مدارج الأعمدة الثلاث. كما تحتوي ثلاثة عناصر متداخلة. وبما أنّ هؤلاء العناصر ليسوا أبناءً مباشرين، فإنهم لن يصبحوا جزءا من الخطاطة الشّبكيّة لذك فالخاصّيةdisplay ستكون خطاطة عادية regular block layout. <div class="wrapper"> <div class="box box1"> <div class="nested">a</div> <div class="nested">b</div> <div class="nested">c</div> </div> <div class="box box2">Two</div> <div class="box box3">Three</div> <div class="box box4">Four</div> <div class="box box5">Five</div> </div> .wrapper { display: grid; grid-template-columns: repeat(3، 1fr); grid-auto-rows: minmax(100px، auto); } .box1 { grid-column-start: 1; grid-column-end: 4; } إذا أضفنا display: contents إلى قواعدbox1، فإنّ المربع الخاصّ بذلك العنصر سيختفي والعناصر الفرعية ستصبح الآن عناصر للشّبكة وستنساب وفق قواعدauto-placement . <div class="wrapper"> <div class="box box1"> <div class="nested">a</div> <div class="nested">b</div> <div class="nested">c</div> </div> <div class="box box2">Two</div> <div class="box box3">Three</div> <div class="box box4">Four</div> <div class="box box5">Five</div> </div> .wrapper { display: grid; grid-template-columns: repeat(3، 1fr); grid-auto-rows: minmax(100px، auto); } .box1 { grid-column-start: 1; grid-column-end: 4; display: contents; } يمكن أن تكون هذه طريقة لجعل العناصر الفرعيّة في الشّبكة تتصرّف كأنها جزء من الشّبكة، وهذا قد يساعدنا على الالتفاف على بعض المشاكل والتي سيتم حلها بواسطة subgrids بمُجرّد تقديمها. يمكنك أيضا استخدام display: contents بطريقة مماثلة مع flexbox لتمكين العناصر الفرعيّة من أن تصبح عناصر مرنة. كما رأينا خلال هذا الدليل، خطاطة CSS الشّبكيّة هي مجرّد أداة واحدة من الأدوات المتاحة لك. لا تتردّد في دمجها مع الطرق الأخرى للتّخطيط لتحصل على التأثيرات التي تصبو إليها. ترجمة -وبتصرّف- للمقال Relationship of grid layout to other layout methods لأصحابه المساهمين (kan199041, VladPavel15, mfluehr, NouranMahmoud, bgates, Pickles-Spill, jarrodn, AleshaOleg, teoli, rachelandrew) حقوق الصورة البارزة محفوظة لـ Freepik
  10. يصف هذا الدرس كيفية استخدام إضافات أندرويد فيKotlin لتحسين دعم تطوير أندرويد. في هذا الدرس سوف نستعرض الخطوات اللازمة لاستخدام ملحقات أندرويد الإضافية في لغة البرمجة Kotlin، لتعزيز تجربة التطوير في أندرويد. ربط العرض View Binding الخلفية كل مطوري أندرويد يعرفون جيدًا الدالة ()findViewById. والتي هي من دون أدنى شك، مصدر لكثير من المتاعب والأخطاء المحتملة والشيفرات السيئة والتي يصعب قراءتها وصيانتها. صحيح أن هناك العديد من المكتبات المتاحة لتوفير حلول لهذه المشكلة، إلّا أن هذه المكتبات تتطلب حقول تأشير annotating fields لكل عنصر معروض من نوع View. توفر لنا ملحقات أندرويد الإضافية لـ Kotlin تجربة مماثلة لما توفره بعض تلك المكتبات، دون أن نكون في حاجة إلى كتابة شيفرات إضافية. في الأساس، هذا يسمح لنا بكتابة الشيفرة التالية: // Using R.layout.activity_main from the 'main' source set import kotlinx.android.synthetic.main.activity_main.* class MyActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Instead of findViewById<TextView>(R.id.textView) textView.setText("Hello, world!") } } textView هي خاصية إضافية لـ Activity، ولها نفس النوع المعلن في activity_main.xml (أي TextView). استخدام إضافات أندرويد لـKotlin إعداد الارتباطات Configuring the Dependency سنستخدم في هذا الدرس Gradle، لكن يمكنك تحقيق نفس النتائج باستخدام IntelliJ IDEA project structure أو Maven. إضافات أندرويد هي جزء من ملحقة Kotlin الخاصة بكل من IntelliJ IDEA وAndroid Studio. لذلك لا تحتاج إلى تثبيت ملحقات إضافية. كل ما تحتاجه هو إتاحة ملحقة Gradle لأندرويد في ملف الوحدة build.gradle: apply plugin: 'kotlin-android-extensions' استيراد الخصائص التركيبية synthetic properties من الملائم استيراد جميع خصائص الودجةwidget) ) لخطاطة (layout) معينة دفعة واحدة: import kotlinx.android.synthetic.main.<layout>.* وهكذا إذا كان اسم ملف الخطاطة هو activity_main.xml، فسنقوم باستيراد kotlinx.android.synthetic.main.activity_main.* إن كنّا نريد أن نستدعي الخصائص التركيبية على View، فيجب علينا أيضًا استيراد kotlinx.android.synthetic.main.activity_main.view.* وبمجرد أن نفعل ذلك، يمكننا حينها استدعاء الإضافات المقابلة والتي هي اسماء خصائص سُمّيت على إثر عناصر العرضviews الموجودة في ملف XML. فعلى سبيل المثال، بالنسبة لهذا العرض: <TextView android:id="@+id/hello" android:layout_width="fill_parent" android:layout_height="wrap_content"/> ستكون هناك خاصية اسمها hello: activity.hello.text = "Hello World!" الوضع التجريبي Experimental Mode تشمل الملحقات الإضافية لأندرويد العديد من الميزات التجريبية مثل دعم LayoutContainer ومولّدات تقديم الصنف Parcelable (Parcelable implementation generator). هذه الميزات لا تُعتبر جاهزة بعدُ للإنتاج، لذلك نحتاج إلى التحوّل للوضع التجريبي في build.gradle من أجل استخدامها: androidExtensions { experimental = true } دعم LayoutContainer تدعم الملحقات الإضافية لأندرويد أنواع مختلفة من الحاويات containers. وأبسط تلك الحاويات Activity، Fragment وView. ولكن يمكنك أن تحوّل (افتراضيًا) أي صنف إلى حاوية لإضافات أندرويد من خلال تطبيق الواجهةLayoutContainer ، على سبيل المثال: import kotlinx.android.extensions.LayoutContainer class ViewHolder(override val containerView: View) : ViewHolder(containerView), LayoutContainer { fun setup(title: String) { itemTitle.text = "Hello World!" } } لاحظ أنك تحتاج إلى التحوّل إلى الوضع التجريبي لاستخدام LayoutContainer. دعم النكهات Flavor Support تدعم ملحقات أندرويد الإضافية نكهات أندرويد ((Android flavors. لنفترض أن لديك نكهة اسمها free في ملف build.gradle خاصّتك: android { productFlavors { free { versionName "1.0-free" } } } يمكنك استيراد كافة الخصائص التركيبية للخطاطة free/res/layout/activity_free.xml بإضافة هذا الاستيراد: import kotlinx.android.synthetic.free.activity_free.* في الوضع التجريبي، يمكنك تحديد أي اسم آخر (وليس فقط flavor)، على سبيل المثال freeDebug أو freeRelease يصلحان كذلك. التخزين المؤقت لعناصرView استدعاء ()findViewById يمكن أن يكون بطيئًا، خصوصًا في حالة تشعبات العرض (view hierarchies) الكبيرة، لذلك تحاول إضافات أندرويد التقليل من عدد مرّات استدعاء ()findViewById بواسطة التخزين المؤقت للعروض في الحاويات. افتراضيًا، اضافات أندرويد تضيف دالة تخزين مؤقت مخفية وحقل تخزين إلى كل حاوية (Activity، Fragment، View أو LayoutContainer implementation) مكتوبة بـ Kotlin. التابع method)) صغير جدًا لذلك لا يزيد حجم APK كثيرًا. في المثال التالي، يتم استدعاء ()findViewById مرة واحدة فقط: class MyActivity : Activity() fun MyActivity.a() { textView.text = "Hidden view" textView.visibility = View.INVISIBLE } لكن في الحالة التالية: fun Activity.b() { textView.text = "Hidden view" textView.visibility = View.INVISIBLE } لا يمكننا أن نعرف ما إذا كان سيتم استدعاء هذه الدالة في أنشطة مصادرنا فقط أم أيضا في كل أنشطة جافا. لهذا السبب، لن نستخدم التخزين المؤقت هنا، حتى لو تم تمرير أحد عيّنات instance الصنف MyActivity من المثال السابق كمستقبِل. تغيير استراتيجية التخزين المؤقت للصنف View يمكنك تغيير استراتيجية التخزين المؤقت بشكل شامل أو بالنسبة لكل حاوية على حدة. وهذا أيضًا يتطلب التحول إلى الوضع التجريبي. يتم تحديد استراتيجية التخزين المؤقت الشاملة للمشروع في ملف build.gradle: androidExtensions { defaultCacheImplementation = "HASH_MAP" // also SPARSE_ARRAY, NONE } افتراضيًا، الملحقات الإضافية لأندرويد تستخدم HashMap كمرجع احتياطي للتخزين، ولكن يمكنك التبديل لتطبيق SparseArray، أو إيقاف التخزين المؤقت وحسب. هذا الأخير مفيد بشكل خاص إن أردت الاكتفاء باستخدام الجزء المقسّم Parcelable من إضافات Android. يمكنك أيضًا التأشير على حاوية ما بـ ContainerOptions@ لتغيير استراتيجية التخزين المؤقت: import kotlinx.android.extensions.ContainerOptions @ContainerOptions(cache = CacheImplementation.NO_CACHE) class MyActivity : Activity() fun MyActivity.a() { // findViewById() will be called twice textView.text = "Hidden view" textView.visibility = View.INVISIBLE } Parcelable بدءًا من الإصدار Kotlin 1.1.4، وفّرت الملحقات الإضافية لأندرويد مولّدات تطبيق للصنف Parcelable كميزة تجريبية. إتاحة دعم Parcelable قم بتطبيق ملحقة Gradle المسمّاة kotlin-android-extensions كما هو موضح [أعلاه] (#إعداد الارتباطات) وقم بتشغيل الوضع التجريبي. كيفية الاستخدام قم بالتأشير على الصنف بـ Parcelize@، وسيتم إنشاء تطبيق Parcelable تلقائيًا. import kotlinx.android.parcel.Parcelize @Parcelize class User(val firstName: String, val lastName: String, val age: Int): Parcelable يتطلّب Parcelize@ التصريح بجميع الخصائص المتسلسلة في المنشئ constructor الأولي. ستقوم إضافات أندرويد بإطلاق تحذير على كل الخصائص ذات الحقول المصرّح بها في جسم الصّنف، كما أنّه لا يمكن تطبيق Parcelize@ إذا لم تكن كل معاملات المنشئ الأوّلية خصائصًا. إن كان صنفك يتطلب تسلسلاً منطقيًا أكثر تقدمًا، فيمكنك كتابته داخل صنف مرافق: @Parcelize data class Value(val firstName: String, val lastName: String, val age: Int) : Parcelable { private companion object : Parceler<User> { override fun User.write(parcel: Parcel, flags: Int) { // Custom write implementation } override fun create(parcel: Parcel): User { // Custom read implementation } } } الأنواع المدعومة يدعم Parcelize@ طيفًا واسعًا من الأنواع: الأنواع الأولية Primitive types (ونسخها المغلّفة boxed versions). Objects وenums . String، CharSequence. Exception. Size، SizeF، Bundle، IBinder، IInterface، FileDescriptor SparseArray، SparseIntArray، SparseLongArray، SparseBooleanArray. كل الأنواع المتسلسلة Serializable (حتى Date مدعوم) وتطبيقات Parcelable. تجميعات كل الأنواع المدعومة: List (مُحالة على ArrayList)، و Set (مُحالة على LinkedHashSet)، و Map (مُحالة على LinkedHashMap). بالإضافة إلى عدد من التطبيقات الملموسة: ArrayList، LinkedList، SortedSet، NavigableSet، HashSet، LinkedHashSet، TreeSet، SortedMap، NavigableMap، HashMap، LinkedHashMap، TreeMap، ConcurrentHashMap. الجداول التي تحتوي الأنواع المدعومة. النسخ الفارغة Nullable versions من كل الأنواع المدعومة. تخصيص الـ Parcelers حتى إن لم يكن النوع مدعوما مباشرة، يمكنك كتابة كائن Parceler لأجل دعمه. class ExternalClass(val value: Int) object ExternalClassParceler : Parceler<ExternalClass> { override fun create(parcel: Parcel) = ExternalClass(parcel.readInt()) override fun ExternalClass.write(parcel: Parcel, flags: Int) { parcel.writeInt(value) } } أمّا عناصر Parcelers الخارجية يمكن تطبيقها باستخدام التأشيرات TypeParceler@ أو WriteWith@: // Class-local parceler @Parcelable @TypeParceler<ExternalClass, ExternalClassParceler>() class MyClass(val external: ExternalClass) // Property-local parceler @Parcelable class MyClass(@TypeParceler<ExternalClass, ExternalClassParceler>() val external: ExternalClass) // Type-local parceler @Parcelable class MyClass(val external: @WriteWith<ExternalClassParceler>() ExternalClass) ترجمة -وبتصرّف- للمقال Kotlin Android Extensions لصاحبه Yan Zhulanow
  11. مقدمة Node.js هو بيئة عمل مفتوحة المصدر لجافا سكريبت تعمل وقت التشغيل لبناء تطبيقات الخادم والشبكات بسهولة. تعمل المنصة على لينكس, OS X, FreeBSD وويندوز. يمكن تشغيل تطبيقات Node.js من سطر الأوامر، ولكن سنركز على تشغيلها كخدمة، بحيث سيتم إعادة تشغيلها تلقائيًا في حال حدوث فشل أو في حال إعادة تشغيل الحاسوب، كما يمكن استخدامها بأمان في بيئة الإنتاج. في هذا الدرس، سوف نغطّي موضوع إنشاء بيئة عمل Node.js جاهزة للإنتاج على خادم Debian 8. هذا الخادم سيشغّل تطبيقًا لـ Node.js سيكون مُدارًا بواسطة PM2، ويوفر للمستخدمين وصولًا آمنًا إلى التطبيق عبر Nginx reverse proxy. المتطلبات الأساسية يفترض هذا الدليل أن لديك خادم Debian 8، بصلاحيات مستخدم غير جذري non-root user ومع امتيازات sudo. ونفترض كذلك أن لديك اسم نطاق يشير إلى عنوان IP العام للخادم. دعونا نبدأ بتثبيت بيئة العمل Node.js على خادمك. تثبيت Node.js سوف نقوم بتثبيت أحدث إصدارات Node.js المدعومة على المدى الطويل، وذلك باستخدام أرشيف الحزمة NodeSource. أولًا، تحتاج إلى تثبيت NodeSource PPA لأجل الوصول إلى محتوياته. تأكد من أنك في المجلد الأساسي home directory، وقم باستخدامcurl للحصول على برنامج التثبيت النصي من مستودعات 6.X Node.js: cd ~ curl -sL https://deb.nodesource.com/setup_6.x -o nodesource_setup.sh يمكنك فحص محتويات هذا النص البرمجي بواسطةnano (أو محرر النصوص المفضل لديك): nano nodesource_setup.sh وقم بتشغيل النص البرمجي عقب sudo: sudo bash nodesource_setup.sh سيتم إضافة PPA إلى إعداداتك وسوف يتم تحديث حزمتك المحلية المُخزنة تلقائيًا. بعد تشغيل برنامج التنصيب من nodesource، يمكنك تثبيت حزمة Node.js بنفس الطريقة التي اتبعتها أعلاه: sudo apt-get install nodejs الحزمة nodejs تحتوي رُقامة nodejs (nodejs binary ) إضافة إلى npm، لذلك لا تحتاج إلى تثبيت npm بشكل منفصل. ولكن لكي تعمل بعض حُزم npm (مثل تلك التي تتطلب ترجمة التعليمات البرمجية من المصدر)، فستحتاج إلى تثبيت الحزمة build-essential: sudo apt-get install build-essential لقد تم تثبيت بيئة العمل Node.js، وصارت جاهزة لتشغيل تطبيقاتنا! لذلك دعونا الآن نكتب تطبيقًا بـ Node.js إنشاء تطبيق Node.js سنكتب تطبيق Hello World والذي سيُرجع ببساطة الجملة “Hello World” لكل طلبات HTTP. هذا تطبيق تعليمي بسيط من شأنه أن يساعدك على التعامل مع Node.js، يمكنك بعد ذلك استبداله بتطبيقاتك الخاصة. فقط تذكر أن تعدّل تطبيقك لكي يُصغي إلى منافذ وعناوين IP المناسبة. الشيفرة البرمجية لـ Hello World أولًا، قم بإنشاء وفتح تطبيق Node.js لأجل التحرير. في هذا الدرس، سوف نستخدم nano لتحرير تطبيق تعليمي يُسمّى hello.js: cd ~ nano hello.js أدرج التعليمات البرمجية التالي في الملف. يمكنك استبدال المنفذ 8080، في كلا الموقعين (تأكد من استخدام منفذ غير أساسي non-admin، أي 1024 أو أكبر): hello.js #!/usr/bin/env nodejs var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(8080, 'localhost'); console.log('Server running at http://localhost:8080/'); الآن احفظ واخرج. هذا التطبيق سيُصغي إلى العنوان المحدد(localhost) والمنفذ (8080)، ويُرجع “Hello World” مع كود النجاح (HTTP success) يساوي200 . وبما أننا نُصغي على localhost، فإن العملاء البعيدين remote clients لن يكونوا قادرين على ربط الاتصال بتطبيقنا. اختبار التطبيق لتتمكن من اختبار التطبيق، اجعل hello.js ملفًّا تنفيذيًا: chmod +x ./hello.js وقم بتشغيله هكذا: ./hello.js Output Server running at http://localhost:8080/ لاختبار التطبيق، قم بفتح جلسة على المطراف terminal على الخادم الخاص بك، ثم اربط االاتصال بـlocalhost بواسطة curl: curl http://localhost:8080 إن رأيت المخرجات التالية، فهذا يعني أن التطبيق يعمل بشكل صحيح ويُصغي للعنوان والمنفذ الصحيحين: Output Hello World إن لم ترى المخرجات المناسبة، تأكد من أن تطبيق Node.js قيد التشغيل، وأنه يٌصغي للعنوان والمنفذ الصحيح. بمجرد أن تتيقن بأنه يعمل، أوقف التطبيق (إذا لم تكن قد فعلت من قبل) عن طريق الضغط على Ctrl + C. تثبيت PM2 سنقوم الآن بتثبيتPM2 ، والذي هو مدير العمليات process manager لتطبيقات Node.js. يوفر PM2 وسيلة سهلة لإدارة وإخفاء daemonize التطبيقات (أي تشغيلها كخدمة في الخلفية.( سوف نستخدم npm، وهي حزمة لإدارة وحدات Nodeالتي تُثبّت مع Node.js، لتثبيت PM2 على الخادم استخدم هذا الأمر: sudo npm install -g pm2 الخيار -g يقول لـnpm أنّ عليه تثبيت الوحدة بشكل كلي globally، بحيث تكون متاحة على نطاق النظام كله. إدارة التطبيق عبر PM2 PM2 بسيط وسهل الاستخدام. سوف نغطي فيما يلي بعض استخداماته الأساسية. بدء التطبيق أول شيء سنفعله هو استخدام التعليمة pm2 start لبدء تشغيل التطبيق hello.js في الخلفية: pm2 start hello.js هذا يضيف أيضًا تطبيقك إلى لائحة عمليات PM2، والتي تُحدَّث في كل مرة تبدأ تشغيل التطبيق: Output [PM2] Spawning PM2 daemon [PM2] PM2 Successfully daemonized [PM2] Starting hello.js in fork_mode (1 instance) [PM2] Done. ┌──────────┬────┬──────┬──────┬────────┬─────────┬────────┬─────────────┬──────────┐ │ App name │ id │ mode │ pid │ status │ restart │ uptime │ memory │ watching │ ├──────────┼────┼──────┼──────┼────────┼─────────┼────────┼─────────────┼──────────┤ │ hello │ 0 │ fork │ 3524 │ online │ 0 │ 0s │ 21.566 MB │ disabled │ └──────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────────────┴──────────┘ Use `pm2 show <id|name>` to get more details about an app كما ترى، يُعيّن PM2 تلقائيا اسم التطبيق (بناءً على اسم الملف دون الامتداد .js). ورقم تعريفPM2 . كما يحفظ PM2 معلومات أخرى، من قبيل معرّف العمليةPID ، وحالته الراهنة، واستخدام الذاكرة. سيتم إعادة تشغيل التطبيقات التي تعمل تحت PM2 تلقائيا إذا تعطل التطبيق أو أُوقف، لكي نجعل التطبيق يشتغل تلقائيًا عند بدء أو إعادة التشغيل ينبغي اتخاذ خطوة إضافية. ولحسن الحظ، يوفّرPM2 وسيلةً سهلةً للقيام بذلك، وهي التعليمة الفرعية startup. تقوم التعليمة الفرعية startup بإنشاء وإعداد برنامج نصي لإطلاق PM2 والعمليات المُدارة من قبله عند بدء تشغيل الخادم. يجب عليك أيضًا تحديد المنصة الذي تعمل عليها، والتي هي Ubuntu، في حالتنا: pm2 startup system السطر الأخير من المخرجات سوف يتضمّن تعليمة عليك تشغيلها بامتيازات المستخدم الجذري superuser. Output [PM2] You have to run this command as root. Execute the following command: sudo env PATH=$PATH:/usr/bin /usr/local/lib/node_modules/pm2/bin/pm2 startup systemd -u sammy --hp /home/Sammy قم بتشغيل التعليمة التي تم إنشاؤها (مماثلة للمخرجات الملوّنة أعلاه، ولكن استخدم اسم المستخدم الخاص بك بدلًا من sammy) لجعل PM2 يبدأ مع بداية التشغيل (استخدم التعليمة من المخرجات التي لديك): sudo env PATH=$PATH:/usr/bin /usr/local/lib/node_modules/pm2/bin/pm2 startup systemd -u sammy --hp /home/sammy هذا سوف يُنشئ systemd unit والتي ستُشغّل PM2 للمستخدم خاصتك عند بدء التشغيل. عينة pm2 هذه ستشغّل بدورهاhello.js . يمكنك التحقق من حالة الوحدة systemd بواسطة systemctl: systemctl status pm2 لمزيد من التفاصيل عن systemd، طالع مقال أساسيات Systemd: العمل مع الخدمات، الوحدات Units، واليوميات Journal استخدامات أخرى لـ PM2 (اختياري) يوفرPM2 العديد من التعليمات الفرعية التي تسمح لك بإدارة أو البحث عن معلومات حول تطبيقاتك. لاحظ أن تشغيل PM2 دون أي معاملات arguments سيؤدي إلى عرض صفحة مساعدة تتضمن أمثلة على الاستخدام والتي تغطي استخدامات PM2 بتفاصيل أكثر مما هو موجود في هذا الدرس. يمكنك إيقاف التطبيق بهذه التعليمة (حدّد App name أو id الخاص بـPM2 ): pm2 stop app_name_or_id يمكنك إعادة تشغيل التطبيق بهذه التعليمة (حدّد App name أو id الخاص بـPM2 ): pm2 restart app_name_or_id ويمكن أيضًا مطالعة قائمة من التطبيقات المُدارة حاليًا من قبل PM2 بالتعليمة الفرعيةlist . pm2 list يمكنك الحصول على مزيد من المعلومات حول تطبيق معين باستخدام التعليمة الفرعية info (حدّد App name أو id الخاص بـPM2 ): pm2 info example يمكن الوصول إلى مراقب عمليات PM2 بالتعليمة الفرعية monit. سيتم عرض حالة التطبيق ووحدة المعالجة المركزية CPU واستخدام الذاكرة: pm2 monit الآن وبعد أن قمنا بتشغيل Node.js وإدارته بواسطة PM2، دعونا نبدأ إعداد الوكيل العكسيreverse proxy . إعداد Nginx ليكون الوكيل العكسي للخادم Reverse Proxy Server الآن وبعد أن بدأ تطبيقك يشتغل ويُصغي إلى المضيف المحلي localhost، تحتاج إلى إعداد وسيلة ليتمكن المستخدمون من الوصول إليه. سوف نقوم بإنشاء خادم Nginx كوكيل عكسي لهذا الغرض. في هذا الدرس ستتعلم كيفية إعداد خادم Nginx من الصفر. إن سبق وقمت بإعداد خادم Nginx، فيمكنك الاكتفاء بنسخ location في server block من اختيارك (تأكد من أن المحل location لا يتداخل مع المحتوى الموجود على الخادم). أولًا، قم بتثبيت Nginx باستخدام apt-get: sudo apt-get install nginx الآن افتح ملف الإعدادات الافتراضي لـ server block لأجل تحريره: sudo nano /etc/nginx/sites-available/default احذف كل ما هو موجود في الملف وقم بإدراج الإعدادات التالية. تأكد من استبدال اسم النطاق خاصتك في الموجّه server_name. بالإضافة إلى ذلك، قم بتغيير المنفذ (8080) إن كان تطبيقك مُعدًّا للإصغاء إلى منفذ آخر: /etc/nginx/sites-available/default server { listen 80; server_name example.com; location / { proxy_pass http://localhost:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } } هذا سيقوم بإعداد الخادم بحيث يرد على الطلبات requests على مستوى الجذر root. إن افترضنا مثلًا أن خادمنا مُتوفر فيexample.com ، فإن الدخول إلى http://example.com/ عبر متصفح الإنترنت سيبعث الطلب إلى hello.js الذي يُصغي إلى المنفذ 8080 في المضيف المحلي. يمكنك إضافة كتل محل location blocks إضافية لنفس كتلة الخادم server block لإتاحة إمكانية الوصول إلى التطبيقات الأخرى على نفس الخادم. على سبيل المثال، إن كنت تُشغّل تطبيقًا آخرًا لـ Node.js على المنفذ 8081، يمكنك إضافة كتلة المحل location block التالية للسماح بالوصول إليها عبر http://example.com/app2 : Nginx Configuration — Additional Locations location /app2 { proxy_pass http://localhost:8081; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } بمجرد الانتهاء من إضافة كتل المحل لتطبيقاتك، قم بالحفظ واخرج. تأكد من أنك لم ترتكب أي أخطاء نحوية syntax errors أثناء الكتابة: sudo nginx –t بعد ذلك، قم بإعادة تشغيل Nginx: sudo systemctl restart nginx بعد ذلك، قم بترخيص المرور لـ Nginx عبر جدار حماية، إن كان متاحًا. إذا كنت تستخدم ufw ، يمكنك استخدام التعليمة التالية: sudo ufw allow 'Nginx Full' يمكنك بواسطةufw التحقق من الحالة باستخدام التعليمة التالية: sudo ufw status إذا كنت تستخدم IPTables بدلًا من ذلك، يمكنك ترخيص المرور لـ Nginx باستخدام التعليمة التالية: sudo iptables -I INPUT -p tcp -m tcp --dport 80 -j ACCEPT يمكنك دائمًا التحقق من حالة IPTables باستخدام التعليمة التالية: sudo iptables –S على افتراض أن تطبيق Node.js الخاص بك قيد التشغيل، وأن إعدادات Nginx وتطبيقاتك صحيحة، فسيكون بإمكانك الآن الوصول إلى تطبيقك عبر وكيل عكسي لـ Nginx. جرّب ذلك بنفسك عن طريق الدخول إلى عنوان الخادم الخاص بك (الـ IP أو اسم النطاق) الخلاصة تهانينا! لقد نجحت في جعل تطبيق Node.js الخاص بك يعمل في خلفية وكيل عكسي لـ Nginx على خادم Debian 8. إعدادات الوكيل العكسي هاته مرنة بما فيه الكفاية لتمكين المستخدمين من الوصول إلى التطبيقات الأخرى أو محتويات صفحات الأنترنت الثابتة التي تريد مشاركتها. حظًا سعيدًا في عملك على Node.js. ترجمة -وبتصرّف- للمقال How To Set Up a Node.js Application for Production on Debian 8 لصاحبته Lisa Tagliaferri
  12. جميعنا – نحن مصمّمي المواقع –تعلّمنا أن نبقى بعيدًا عن الخُطاطات layouts المستندة على الجداول. لهذا غالبًا ما ننساها ونهملها إلى أن نجد أنفسنا محتاجين إليها. هذا الدرس سيأخذك خطوة بخطوة لإنشاء جدول بيانات أنيق وسلس يحتوي مقارنة بين مميّزات عدة درّاجات نارية من طرازHarley Davidson . سنقوم ببناء الجدول حصرًا بـ HTML ثمّ سنصقله بـ CSS لإنشاء جدول HTML جميل وواضح. الجدول سيقارن ميزات ثلاثة نماذج من الدراجات النارية من طراز Harley Davidson Sportster. سيتمّ بناء الجدول بـ HTML ثمّ سنُنسّقه بـ CSS لجعل البيانات واضحة. مشاهدة الجدول النّهائي سنحتاج إلى عدّة ملفات لأجل تصميم هذا المشروع التّعليمي. وهي: صورة PNG سنستخدمها كخلفية، وصورة كبيرة بالأبيض والأسود لملء الخلفيّة، وشعار Harley Davidson إضافة إلى صور نماذج الدرّاجات النارية الثلاث التي سنقارنها. الملفّ عبارة عن HTML عاديّ. حيث يبدأ بـ DOCTYPE، عنوان الصفحة ورابط لـ CSS. يبدأ محتوى الصفحة بـ <H1>، والذي سيتم لاحقا تحويله إلى شعار HD، بعد ذلك سنضيف وعاء div لمساعدتنا على توسيط المحتوى. بعد ذلك سندرج <table> متبوعًا بـ <THEAD> لتحديد العناوين والتّرويسات headings في جدولنا. يحتوي <THEAD> على صفّين، أحدهما يتضمّن صور الدرّاجات والآخر يتضمّن عناوين كل نموذج في وسم <H2>. الجدول يحتوي ثلاثة أعمدة، ولكن الخلايا الأولى في الترويسة فارغة لذلك سنضيف الحرف الرّابط &nbsp;. لمساعدتنا على تنسيق الجدول وتحسين مقروئية البيانات وفي نفس الوقت سنضيف في الكود أصنافًا classes إلى الخلايا. بعد إغلاق <THEAD> يبدأ <TBODY>. وبينما تُستخدم <th> داخل <THEAD>، فإنّ <td> تستخدم داخل <TBODY>. كل سلسة من الخلايا مُتضمَّنة داخل صفّ واحد، وصفوفنا تساعدنا في تحديد الأعمدة. كما ستتم إضافة كافّة صفوف البيانات لإنهاء HTML. HTML النّهائي <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Harley Davidson Sportster Motorcycle Model Comparison</title> <link href="style.css" rel="stylesheet" /> </head> <body> <h1>Harley Davidson Motorcycles</h1> <div id="container"> <table> <thead> <tr> <th> </th> <th class="iron"><img src="images/iron.jpg" alt="Harley Davidson Iron 883" /></th> <th class="nightster"><img src="images/nightster.jpg" alt="Harley Davidson Nightster" /></th> <th class="fortyeight"><img src="images/forty-eight.jpg" alt="Harley Davidson Forty-Eight" /></th> </tr> <tr> <th> </th> <th class="iron"><h2>Iron 883</h2></th> <th class="nightster"><h2>Nightster</h2></th> <th class="fortyeight"><h2>Forty-Eight</h2></th> </tr> </thead> <tbody> <tr> <td class="feature">Engine</td> <td class="iron">883cc</td> <td class="nightster">1202cc</td> <td class="fortyeight">1202cc</td> </tr> <tr> <td class="feature">Torque</td> <td class="iron">70Nm</td> <td class="nightster">98Nm</td> <td class="fortyeight">98Nm</td> </tr> <tr> <td class="feature">Exhaust</td> <td class="iron">Chrome, staggered shorty exhaust with dual mufflers</td> <td class="nightster">Chrome, slash-cut ex-haust with dual mufflers</td> <td class="fortyeight">Chrome, staggered shorty exhaust with dual slash-cut mufflers</td> </tr> <tr> <td class="feature">Wheels</td> <td class="iron">Black, 13-Spoke Cast Alumi-num</td> <td class="nightster">Black, Laced Steel</td> <td class="fortyeight">Black, Laced Steel</td> </tr> <tr> <td class="feature">Ground Clearance</td> <td class="iron">120mm</td> <td class="nightster">130mm</td> <td class="fortyeight">100mm</td> </tr> <tr> <td class="feature">Price</td> <td class="iron">£6,699</td> <td class="nightster">£8,099</td> <td class="fortyeight">£8,849</td> </tr> </tbody> </table> </div> </body> </html> يبدأ CSS بإزالة وتعويض تنسيق المتصفّح الافتراضي، ثم يقوم بتعيين التنسيق الكلّي للصّفحة. بعد ذلك نضيف صورة الخلفية إلى جسم الصّفحة. أمّا خصائص الخط العّام فستكون16px Georgia رمادي. بعد ذلك سيتمّ تحويل <H1> إلى شعار Harley Davidson باستخدام تقنية استبدال الصورة، ثم سنضع الوعاء div في وسط الصفحة. تُستخدم صورة الخلفية النّمطية للملء. وبعدها سنحدّد قيم الخاصّية box-shadow لـ CSS3 لمحاكاة تأثير الظّل المُنسدل في فوتوشوب. افتراضيًا سيعرض الجدول فجوات صغيرة بين خلايا الجدول. التصميم الذي نبتغي يتطلّب هامشًا بين الأعمدة ولكن دون ثغرات بين الصّفوف. تسمح لنا الخاصيّة border-spacing بضبط التباعد على المحورين Y و X. ستتمّ إضافة الهامش الداخلي Padding, كما سيتمّ توسيط النص في جميع العناصر <th> و <td>، ثم سنستثني الخلايا ذات الصّنف “feature”، حيث سنحاذي النصّوص فيها إلى اليسار. كما ستُمنح هذه الخلايا عرضًاwidth محدّدًا لتغيير تناسب الجدول لجعل ذلك العمود الأكبر من بين بقيّة الأعمدة. أعطينا لكلّ الأصناف “iron”، “nightster” و “fortyeight” خلفيّة بيضاء شفّافة باستخدام RGBa. كان بإمكاننا أن نستخدم صنفًا واحدًا لجميع هذه الخلايا، ولكنّ الأصناف المعيّنة ستساعدنا على التنقل في بيانات الجدول في الكود البرمجي. لأجل إضافة لمسة أخيرة على الجدول، سنضيف نفس الملء الشفّاف إلى صفوف الجدول، ولكن فقط عندما يطوف عليها مؤشّر الفأرة. هذا التأثير البسيط يعزّز سهولة استخدام الجدول، ممّا يساعد المستخدم على مطالعة ومقارنة البيانات. CSS الكامل body, div, h1, h2, h3, h4, h5, h6, p, ul, ol, li, dl, dt, dd, img, form, fieldset, input, textarea, blockquote, table, tr, td, th { margin: 0; padding: 0; border: 0; } body { background: #000 url(images/bg.jpg) center top fixed no-repeat; font: 16px Georgia, Serif; color: #ccc; } h1 { width: 168px; height: 130px; margin: 30px auto; position: relative; background: url(images/harley-davidson.png); text-indent: -9999px; } #container { width: 940px; margin: -115px auto; padding: 110px 10px 50px 10px; background: url(images/bg-pattern.png); box-shadow: 0px 0px 15px #000; } table { border-spacing: 10px 0px; } th, td { text-align: center; padding: 10px; } .feature { width: 183px; text-align: right; font-size: 24px; font-weight: normal; color: #fff; } .iron, .nightster, .fortyeight { background: rgba(255,255,255,0.05); } h2 { font-size: 24px; font-weight: normal; color: #fff; } tr:hover { background: rgba(255,255,255,0.05); } thead tr:hover { background: none; } جدول HTML / CSS النهائي تحقّق بنفسك من المثال لرؤية الشكل النهائي للجدول بكلّ التّأثيرات التي ترافقه. استخدام الشفافية من ملفّات PNG24 وصيغة التّلوينRGBa ساعد على إنشاء تصميم أنيق عندما دُمج مع صورة الخلفية الكبيرة. عمومًا تقنيات الجدول البسيطة هذه يمكن استخدامها في أيّ مشروع لعرض بياناتك الجدولية بطريقة واضحة وسهلة الفهم. ترجمة -وبتصرّف- للمقال How To Create a Slick Features Table in HTML & CSS لصاحبه iggy
  13. في هذا الدرس ستتعلّم كيفيّة إنشاء تطبيق Kotlin بسيط لأندرويد باستخدام Android Studio. تثبيت ملحقة Kotlin تمّ إدماج ملحقة Kotlin مع Android Studio بدءًا من النسخة 3.0. إن كنت تستخدم إصدارًا سابقًا، فستحتاج تثبيت ملحقة Kotlin. اذهب إلى File | Settings | Plugins | Install JetBrains plugin… بعد ذلك ابحث عنKotlin وقم بتثبيته. إن كنت تبحث في الشاشة " Welcome to Android Studio"، فقم باختيار Configure | Plugins | Install JetBrains plugin… عليك إعادة تشغيل بيئة التطوير بعد الانتهاء. إنشاء مشروع من السهل للغاية البدء في استخدام Kotlin لتطوير أندرويد. في هذا الدرس سنعمل على Android Studio. ولكن إن كنت تستخدم Intellij IDEA مع أندرويد، فالعمليّة هي نفسها تقريبًا. لنقم أوّلاً بإنشاء مشروع جديد. اختر Start a new Android Studio project أو File | New project. سيساعدك صندوق الحوار التالي في عملية إنشاء مشروع جديد. ستحتاج إلى اختيار اسم للمشروع وتحديد أيّ إصدارات Android SDK قمت بتثبيتها. معظم الخيارات يمكن إبقاؤها على قيمها الافتراضية، لذلك يمكنك الضغط على Enter مرارًا. تسميّة المشروع: يوفر Android Studio 3.0 خيارًا لتمكين دعم Kotlin على هذه الشاشة. يمكنك التحقق من هذا الخيار وتجاوز الخطوة " Configuring Kotlin in the project" أدناه. اختيار إصدار الأندرويد: اختر إنشاء النشاط الذي سيتم تجهيزه لك: قم بتسمية النّشاط: في Android Studio 3.0، يمكنك أن تختار إنشاء النشاط في Kotlin على الفور، حتى تتمكن من تجاوز الخطوة “Converting Java code to Kotlin”. الإصدارات السابقة تخلق النشاط في Java، ويمكنك بعدها استخدام أداة التحويل الآلي لتحويله. بشكل عام، أسهل طريقة للبدء في استخدام Kotlin هي بالتحويل التلقائي لنشاط جافا إلى نشاط Kotlin. يرجى ملاحظة أنه بدلاً من البحث في الوثائق عن وسيلة جديدة للتعبير عن نمط برمجي قديم، يمكنك كتابته بالجافا، ثم نسخ-لصق كود الجافا في ملف Kotlin، وسيقترح عليك IntelliJ IDEA (أو Android Studio) تحويله. تحويل كود الجافا إلى Kotlin افتح ملف MainActivity.java. ثم قم باستدعاء الإجراء Convert Java File to Kotlin File. يمكنك القيام بذلك بعدّة طرق أسهلها استدعاء Find Action ثمّ البدء في كتابة اسم الإجراء (كما هو مبيّن في المقتطف أدناه). أو يمكنك بدلا من ذلك استدعاء هذا الخيار Code | Convert Java File to Kotlin File من القائمة أو استخدام الاختصار المقابل (يمكنك العثور عليه في عنصر القائمة). بعد التحويل من المفروض أن تحصل على نشاط مكتوب بـ Kotlin. إعداد Kotlin في المشروع إن بدأت في تحرير هذا الملف، Android Studio سيُلمّح لك في الموجّه prompt بأنّ Kotlin لم يتم إعداده بعد لكي تقوم بإعداده. أو بدلاً من ذلك، يمكنك أن تبدأ الإعداد بأن تختار Tools | Kotlin | Configure Kotlin in Project من القائمة الرئيسيّة. ثم ستتم مطالبتك بتحديد إصدار Kotlin. قم باختيار أحدث الإصدارات المتاحة في قائمة الإصدارات المثبّتة. بعد إعداد Kotlin، ينبغي تحديث ملف التطبيق build.gradle. وحينها يمكنك أن ترى أنه تمّت إضافة ملحقة التطبيق: "kotlin-android" ومكتبتها المتعلّقة kotlin-stdlib. الخطوة الأخيرة هي مزامنة المشروع. يمكنك الضغط على “Sync Now” في الموجّه أو استدعاء الإجراء Sync Project with Gradle Files. بناء ونشر تطبيقات Kotlin لأندرويد أنت الآن جاهز لإنشاء التطبيق وتشغيله على محاكٍ أو جهاز. وهذا يجري تمامًا بنفس الطريقة في جافا. يمكنك إطلاق التطبيق والتوقيع عليه على غرار ما تفعله في تطبيقات أندرويد المكتوبة بلغة جافا. حجم ملف تشغيل Kotlin صغير: فحجم المكتبة في حدود 932 KB (الإصدار 1.2.10). وهذا يعني أنّ Kotlin لا يضيف إلا قدرًا قليلًا إلى حجم الملف apk. يُنتج مترجم Kotlin رُقامات byte-code، وبالتالي فليس هناك حقّا أيّ فرق من حيث الشكل والمظهر بين التطبيقات المكتوبة بـ Kotlin وتلك المكتوبة بلغة جافا. من توثيقيات Kotlin
  14. السلام عليكم, بعد بحث مطول عن أفضل بيئة عمل لتطوير تطبيقات الهاتف استقريت على اختيار ionic. لكن قبل أن أبدأ تعلمه يراودني هاجس يؤرق كل مبرمج عربي, وهو مشكلة دعم اللغة العربية. سؤالي هو: هل يمكن إنشاء تطبيقات على ionic باللغة العربية دون مشاكل,
  15. شكرا أخي أحمد ماذا عن الخيار الثاني html/css/javascript/jquiry هل يمكنني الاكتفاء بها لبرمجة التطبيقات أم ان فيها نفس مشكل بايثون