Gulp: سلاح سري لمطور الويب لزيادة سرعة الموقع

نشرت: 2022-03-11

يتعين على الكثير منا التعامل مع المشاريع القائمة على الويب والتي تُستخدم في الإنتاج ، والتي تقدم خدمات متنوعة للجمهور. عند التعامل مع مثل هذه المشاريع ، من المهم أن تكون قادرًا على بناء ونشر الكود الخاص بنا بسرعة. غالبًا ما يؤدي القيام بشيء ما بسرعة إلى حدوث أخطاء ، خاصةً إذا كانت العملية متكررة ، لذلك من الجيد أتمتة مثل هذه العملية قدر الإمكان.

Gulp: سلاح سري لمطور الويب لزيادة سرعة الموقع

زملائي المطورين: ليس هناك أي عذر لخدمة البريد غير الهام لمتصفحك.
سقسقة

في هذا المنشور ، سنبحث في أداة يمكن أن تكون جزءًا مما سيسمح لنا بتحقيق مثل هذه الأتمتة. هذه الأداة عبارة عن حزمة npm تسمى Gulp.js. من أجل التعرف على مصطلحات Gulp.js الأساسية المستخدمة في هذا المنشور ، يرجى الرجوع إلى "مقدمة إلى أتمتة JavaScript مع Gulp" التي تم نشرها مسبقًا على المدونة بواسطة أنطونيوس ميناس ، أحد مطوري Toptal الزملاء. سنفترض معرفة أساسية ببيئة npm ، حيث يتم استخدامها على نطاق واسع خلال هذا المنشور لتثبيت الحزم.

خدمة الأصول الأمامية

قبل أن نواصل ، دعنا نعود بضع خطوات إلى الوراء للحصول على نظرة عامة حول المشكلة التي يمكن أن يحلها Gulp.js لنا. تتميز العديد من المشاريع المستندة إلى الويب بملفات JavaScript أمامية يتم تقديمها للعميل من أجل توفير وظائف متنوعة لصفحة الويب. عادة ما تكون هناك أيضًا مجموعة من أوراق أنماط CSS التي يتم تقديمها للعميل أيضًا. في بعض الأحيان عند النظر إلى الكود المصدري لموقع ويب أو تطبيق ويب ، يمكننا أن نرى رمزًا مثل هذا:

 <link href="css/main.css" rel="stylesheet"> <link href="css/custom.css" rel="stylesheet"> <script src="js/jquery.min.js"></script> <script src="js/site.js"></script> <script src="js/module1.js"></script> <script src="js/module2.js"></script>

هناك بعض المشاكل مع هذا الرمز. يحتوي على مراجع إلى صفحتين منفصلتين من أنماط CSS وأربعة ملفات JavaScript منفصلة. هذا يعني أنه يجب على الخادم تقديم إجمالي ستة طلبات إلى الخادم ، ويجب على كل طلب تحميل مورد بشكل منفصل قبل أن تصبح الصفحة جاهزة. هذه مشكلة أقل مع HTTP / 2 لأن HTTP / 2 يقدم التوازي وضغط الرأس ، لكنه لا يزال يمثل مشكلة. فهو يزيد من الحجم الإجمالي لحركة المرور المطلوبة لتحميل هذه الصفحة ويقلل من جودة تجربة المستخدم لأن تحميل الملفات يستغرق وقتًا أطول. في حالة HTTP 1.1 ، فإنه يستحوذ أيضًا على الشبكة ويقلل من عدد قنوات الطلب المتاحة. كان من الأفضل دمج ملفات CSS و JavaScript في حزمة واحدة لكل منهما. بهذه الطريقة ، لن يكون هناك سوى طلبين إجمالاً. كان من الجيد أيضًا تقديم إصدارات مصغرة من هذه الملفات ، والتي عادة ما تكون أصغر بكثير من النسخ الأصلية. قد يتعطل تطبيق الويب الخاص بنا أيضًا إذا تم تخزين أي من الأصول مؤقتًا ، وسيتلقى العميل إصدارًا قديمًا.

الزائد

تتمثل إحدى الطرق البدائية لحل بعض هذه المشكلات في دمج كل نوع من الأصول يدويًا في حزمة باستخدام محرر نص ، ثم تشغيل النتيجة من خلال خدمة أداة تصغير ، مثل http://jscompress.com/. هذا يثبت أنه شاق للغاية للقيام به باستمرار أثناء عملية التطوير. قد يكون التحسين الطفيف المشكوك فيه هو استضافة خادم المصغر الخاص بنا ، باستخدام إحدى الحزم المتوفرة على GitHub. ثم يمكننا القيام بأشياء تشبه إلى حد ما ما يلي:

 <script src="min/f=js/site.js,js/module1.js"></script>

هذا من شأنه أن يخدم الملفات المصغرة لعملائنا ، لكنه لن يحل مشكلة التخزين المؤقت. قد يتسبب ذلك أيضًا في تحميل إضافي على الخادم نظرًا لأن خادمنا سيتعين عليه بشكل أساسي ربط جميع ملفات المصدر وتصغيرها بشكل متكرر عند كل طلب.

أتمتة مع Gulp.js

بالتأكيد يمكننا أن نفعل ما هو أفضل من أي من هذين النهجين. ما نريده حقًا هو أتمتة التجميع وإدراجه في مرحلة بناء مشروعنا. نريد أن ننتهي بحزم أصول مسبقة الإنشاء تم تصغيرها بالفعل وجاهزة للخدمة. نريد أيضًا إجبار العميل على تلقي أحدث الإصدارات من أصولنا المجمعة عند كل طلب ، لكننا ما زلنا نرغب في الاستفادة من التخزين المؤقت إن أمكن. لحسن الحظ بالنسبة لنا ، يمكن لـ Gulp.js التعامل مع ذلك. في الجزء المتبقي من المقالة ، سنبني حلاً من شأنه الاستفادة من قوة Gulp.js لربط الملفات وتصغيرها. سنستخدم أيضًا مكونًا إضافيًا لإفساد ذاكرة التخزين المؤقت عندما تكون هناك تحديثات.

سننشئ بنية الدليل والملف التالية في مثالنا:

 public/ |- build/ |- js/ |- bundle-{hash}.js |- css/ |- stylesheet-{hash}.css assets/ |- js/ |- vendor/ |- jquery.js |- site.js |- module1.js |- module2.js |- css/ |- main.css |- custom.css gulpfile.js package.json
يجعل npm إدارة الحزم في مشاريع Node.js نعمة. يوفر Gulp قابلية تمدد هائلة من خلال الاستفادة من نهج التغليف البسيط لـ npm لتقديم مكونات إضافية قوية وقوية.

ملف gulpfile.js هو المكان الذي سنحدد فيه المهام التي سيؤديها Gulp لنا. يتم استخدام package.json بواسطة npm لتحديد حزمة تطبيقنا وتتبع التبعيات التي سنقوم بتثبيتها. الدليل العام هو ما يجب تهيئته لمواجهة الويب. دليل الأصول هو المكان الذي سنخزن فيه ملفات المصدر الخاصة بنا. لاستخدام Gulp في المشروع ، سنحتاج إلى تثبيته عبر npm وحفظه باعتباره تبعية مطور للمشروع. سنرغب أيضًا في البدء باستخدام المكوّن الإضافي concat الخاص بـ Gulp ، والذي سيسمح لنا بجمع عدة ملفات في ملف واحد.

لتثبيت هذين العنصرين ، سنقوم بتشغيل الأمر التالي:

 npm install --save-dev gulp gulp-concat

بعد ذلك ، سنريد البدء في كتابة محتوى gulpfile.js.

 var gulp = require('gulp'); var concat = require('gulp-concat'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(gulp.dest('public/build/js')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(gulp.dest('public/build/css')); }); gulp.task('default', ['pack-js', 'pack-css']);

هنا ، نقوم بتحميل مكتبة gulp والمكوِّن الإضافي concat الخاص بها. ثم نحدد ثلاث مهام.

تحميل مكتبة gulp ومكوِّن concat الإضافي الخاص بها

تحدد المهمة الأولى ( pack-js ) إجراءً لضغط عدة ملفات مصدر JavaScript في حزمة واحدة. نقوم بإدراج ملفات المصدر ، والتي سيتم مسحها وقراءتها وتسلسلها بالترتيب المحدد. قمنا بتوجيه ذلك إلى المكون الإضافي concat للحصول على ملف نهائي واحد يسمى bundle.js . أخيرًا ، نطلب من gulp كتابة الملف إلى public/build/js .

المهمة الثانية ( pack-css ) تفعل نفس الشيء كما هو مذكور أعلاه ، ولكن لأوراق أنماط CSS. يخبر Gulp بتخزين الإخراج المتسلسل كـ stylesheet.css في public/build/css .

المهمة الثالثة ( default ) هي المهمة التي يديرها Gulp عندما نستدعيها بدون وسيطات. في المعلمة الثانية ، نقوم بتمرير قائمة المهام الأخرى المطلوب تنفيذها عند تشغيل المهمة الافتراضية.

دعنا نلصق هذا الرمز في gulpfile.js باستخدام أي محرر شفرة مصدر نستخدمه عادةً ، ثم احفظ الملف في جذر التطبيق.

بعد ذلك ، سنفتح سطر الأوامر ونشغل:

 gulp

إذا نظرنا إلى ملفاتنا بعد تشغيل هذا الأمر ، فسنجد ملفين جديدين: public/build/js/bundle.js و public/build/css/stylesheet.css . إنها سلاسل من ملفات المصدر الخاصة بنا ، والتي تحل جزءًا من المشكلة الأصلية. ومع ذلك ، لم يتم تصغيرها ، ولا يوجد خرق لذاكرة التخزين المؤقت حتى الآن. دعونا نضيف التصغير الآلي.

تحسين الأصول المبنية

سنحتاج اثنين من الإضافات الجديدة. لإضافتهم ، سنقوم بتشغيل الأمر التالي:

 npm install --save-dev gulp-clean-css gulp-minify

المكون الإضافي الأول مخصص لتصغير CSS ، والثاني مخصص لتصغير JavaScript. الأول يستخدم الحزمة clean-css ، والثاني يستخدم الحزمة UglifyJS2. سنقوم بتحميل هاتين الحزمتين في gulpfile.js أولاً:

 var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css');

سنحتاج بعد ذلك إلى استخدامها في مهامنا قبل أن نكتب الإخراج على القرص:

 .pipe(minify()) .pipe(cleanCss())

يجب أن يبدو ملف gulpfile.js كما يلي:

 var gulp = require('gulp'); var concat = require('gulp-concat'); var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(minify()) .pipe(gulp.dest('public/build/js')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(cleanCss()) .pipe(gulp.dest('public/build/css')); }); gulp.task('default', ['pack-js', 'pack-css']);

دعونا نجري بلع مرة أخرى. سنرى أن ملف stylesheet.css محفوظ بتنسيق مصغر ، ولا يزال ملف bundle.js محفوظًا كما هو. سنلاحظ أن لدينا الآن أيضًا bundle-min.js ، وهي مُصغرة. نريد فقط الملف المصغر ، ونريد حفظه كـ bundle.js ، لذلك سنقوم بتعديل الكود الخاص بنا بمعلمات إضافية:

 .pipe(minify({ ext:{ min:'.js' }, noSource: true }))

وفقًا لوثائق البرنامج المساعد gulp-minify (https://www.npmjs.com/package/gulp-minify) ، سيؤدي هذا إلى تعيين الاسم المطلوب للإصدار المصغر وإخبار المكون الإضافي بعدم إنشاء الإصدار الذي يحتوي على المصدر الأصلي. إذا حذفنا محتوى دليل الإنشاء وقمنا بتشغيل gulp من سطر الأوامر مرة أخرى ، فسننتهي بملفين مصغرين فقط. لقد انتهينا للتو من تنفيذ مرحلة التصغير من عملية البناء الخاصة بنا.

خرق مخبأ

بعد ذلك ، سنرغب في إضافة خرق ذاكرة التخزين المؤقت ، وسنحتاج إلى تثبيت مكون إضافي لذلك:

 npm install --save-dev gulp-rev

ونطلبها في ملف الجلب الخاص بنا:

 var rev = require('gulp-rev');

استخدام البرنامج المساعد صعب بعض الشيء. يتعين علينا توجيه الإخراج المصغر من خلال المكون الإضافي أولاً. بعد ذلك ، يتعين علينا استدعاء المكون الإضافي مرة أخرى بعد كتابة النتائج على القرص. يقوم المكون الإضافي بإعادة تسمية الملفات بحيث يتم تمييزها بعلامة تجزئة فريدة ، كما يقوم أيضًا بإنشاء ملف بيان. ملف البيان هو خريطة يمكن أن يستخدمها تطبيقنا لتحديد أحدث أسماء الملفات التي يجب أن نشير إليها في كود HTML الخاص بنا. بعد تعديل ملف gulp ، يجب أن يبدو في النهاية كما يلي:

 var gulp = require('gulp'); var concat = require('gulp-concat'); var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css'); var rev = require('gulp-rev'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(minify({ ext:{ min:'.js' }, noSource: true })) .pipe(rev()) .pipe(gulp.dest('public/build/js')) .pipe(rev.manifest()) .pipe(gulp.dest('public/build')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(cleanCss()) .pipe(rev()) .pipe(gulp.dest('public/build/css')) .pipe(rev.manifest()) .pipe(gulp.dest('public/build')); }); gulp.task('default', ['pack-js', 'pack-css']);
مع ضبط ذاكرة التخزين المؤقت المناسبة في مكانها ، يمكنك قضاء وقت طويل في انتهاء صلاحية ملفات JS و CSS واستبدالها بشكل موثوق بإصدارات أحدث عند الضرورة.

دعنا نحذف محتويات دليل البناء الخاص بنا ونشغل gulp مرة أخرى. سنجد أن لدينا الآن ملفين بهما علامات تصنيف ملحقة بكل اسم ملف ، و manifest.json محفوظ في public/build . إذا فتحنا ملف البيان ، فسنرى أنه يحتوي فقط على مرجع لأحد الملفات المصغرة والمميزة بعلامات. ما يحدث هو أن كل مهمة تكتب ملف بيان منفصل ، وينتهي أحدهم بالكتابة فوق الآخر. سنحتاج إلى تعديل المهام بمعلمات إضافية تخبرهم بالبحث عن ملف البيان الحالي ودمج البيانات الجديدة فيه إذا كان موجودًا. بناء الجملة الخاص بذلك معقد بعض الشيء ، لذلك دعونا نلقي نظرة على الشكل الذي يجب أن تبدو عليه الشفرة ثم ننتقل إليها:

 var gulp = require('gulp'); var concat = require('gulp-concat'); var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css'); var rev = require('gulp-rev'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(minify({ ext:{ min:'.js' }, noSource: true })) .pipe(rev()) .pipe(gulp.dest('public/build/js')) .pipe(rev.manifest('public/build/rev-manifest.json', { merge: true })) .pipe(gulp.dest('')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(cleanCss()) .pipe(rev()) .pipe(gulp.dest('public/build/css')) .pipe(rev.manifest('public/build/rev-manifest.json', { merge: true })) .pipe(gulp.dest('')); }); gulp.task('default', ['pack-js', 'pack-css']);

نحن نقوم بتوصيل الإخراج إلى rev.manifest() أولاً. يؤدي هذا إلى إنشاء ملفات ذات علامات بدلاً من الملفات التي كانت لدينا من قبل. نحن نقدم المسار المطلوب لـ rev-manifest.json الخاص بنا ، rev.manifest() بالاندماج في الملف الحالي ، إذا كان موجودًا. ثم نطلب من gulp كتابة البيان إلى المجلد الحالي ، والذي سيكون في هذه المرحلة public / build. ترجع مشكلة المسار إلى خطأ تمت مناقشته بمزيد من التفاصيل على GitHub.

لدينا الآن تصغير تلقائي وملفات ذات علامات وملف بيان. كل هذا سيسمح لنا بتسليم الملفات بسرعة أكبر للمستخدم ، وكسر ذاكرة التخزين المؤقت الخاصة بهم كلما أجرينا تعديلاتنا. هناك مشكلتان متبقيتان على الرغم من ذلك.

المشكلة الأولى هي أنه إذا أجرينا أي تعديلات على ملفات المصدر الخاصة بنا ، فسنحصل على الملفات التي تم وضع علامات عليها حديثًا ، ولكن ستبقى الملفات القديمة هناك أيضًا. نحتاج إلى طريقة ما لحذف الملفات المصغرة القديمة تلقائيًا. دعنا نحل هذه المشكلة باستخدام مكون إضافي يسمح لنا بحذف الملفات:

 npm install --save-dev del

سنطلبه في الكود الخاص بنا ونحدد مهمتين جديدتين ، واحدة لكل نوع من أنواع الملفات المصدر:

 var del = require('del'); gulp.task('clean-js', function () { return del([ 'public/build/js/*.js' ]); }); gulp.task('clean-css', function () { return del([ 'public/build/css/*.css' ]); });

سنتأكد بعد ذلك من انتهاء تشغيل المهمة الجديدة قبل القيام بمهمتين رئيسيتين:

 gulp.task('pack-js', ['clean-js'], function () { gulp.task('pack-css', ['clean-css'], function () {

إذا قمنا بتشغيل gulp مرة أخرى بعد هذا التعديل ، فسنحصل فقط على أحدث الملفات المصغرة.

المشكلة الثانية هي أننا لا نريد الاستمرار في تناول جرعة في كل مرة نقوم فيها بإجراء تغيير. لحل هذه المشكلة ، سنحتاج إلى تحديد مهمة مراقب:

 gulp.task('watch', function() { gulp.watch('assets/js/**/*.js', ['pack-js']); gulp.watch('assets/css/**/*.css', ['pack-css']); });

سنقوم أيضًا بتغيير تعريف مهمتنا الافتراضية:

 gulp.task('default', ['watch']);

إذا قمنا الآن بتشغيل gulp من سطر الأوامر ، فسنجد أنه لم يعد يبني أي شيء عند الاستدعاء. هذا لأنه يستدعي الآن مهمة المراقب التي ستراقب ملفات المصدر الخاصة بنا بحثًا عن أي تغييرات ، وتبني فقط عندما تكتشف تغييرًا. إذا حاولنا تغيير أي من ملفات المصدر الخاصة بنا ثم نظرنا إلى وحدة التحكم الخاصة بنا مرة أخرى ، فسنرى أن مهام pack-js و pack-css تعمل تلقائيًا جنبًا إلى جنب مع تبعياتها.

الآن ، كل ما يتعين علينا القيام به هو تحميل ملف manifest.json في تطبيقنا والحصول على أسماء الملفات ذات العلامات من ذلك. تعتمد طريقة قيامنا بذلك على لغتنا الخلفية الخاصة ومكدس التكنولوجيا لدينا ، وسيكون تنفيذه تافهًا للغاية ، لذلك لن نتطرق إليه بالتفصيل. ومع ذلك ، فإن الفكرة العامة هي أنه يمكننا تحميل البيان في مصفوفة أو كائن ، ثم تحديد وظيفة مساعد تسمح لنا باستدعاء الأصول التي تم إصدارها من قوالبنا بطريقة مشابهة لما يلي:

 gulp('bundle.js')

بمجرد القيام بذلك ، لن نضطر إلى القلق بشأن العلامات التي تم تغييرها في أسماء الملفات الخاصة بنا مرة أخرى ، وسنكون قادرين على التركيز على كتابة تعليمات برمجية عالية الجودة.

يمكن العثور على الكود المصدري النهائي لهذه المقالة ، جنبًا إلى جنب مع بعض نماذج الأصول ، في مستودع GitHub هذا.

خاتمة

في هذه المقالة ، استعرضنا كيفية تنفيذ الأتمتة القائمة على Gulp لعملية البناء الخاصة بنا. آمل أن يكون هذا مفيدًا لك ويسمح لك بتطوير عمليات بناء أكثر تعقيدًا في تطبيقاتك الخاصة.

يرجى أن تضع في اعتبارك أن Gulp هي مجرد واحدة من الأدوات التي يمكن استخدامها لهذا الغرض ، وهناك العديد من الأدوات الأخرى مثل Grunt و Browserify و Webpack. وهي تختلف في أغراضها وفي نطاق المشكلات التي يمكنها حلها. يمكن للبعض حل المشكلات التي لا يستطيع Gulp حلها ، مثل تجميع وحدات JavaScript النمطية مع التبعيات التي يمكن تحميلها عند الطلب. يُشار إلى هذا باسم "تقسيم الكود" ، وهو تحسين على فكرة تقديم ملف واحد كبير مع جميع أجزاء برنامجنا في كل صفحة. هذه الأدوات متطورة للغاية ولكن قد يتم تغطيتها في المستقبل. في المنشور التالي ، سوف ننتقل إلى كيفية أتمتة نشر تطبيقنا.

الموضوعات ذات الصلة: Gulp Under the Hood: إنشاء أداة لأتمتة المهام قائمة على التدفق