شعار زيفيرنت

مقدمة إلى esbuild Bundler - SitePoint

التاريخ:

esbuild عبارة عن أداة تجميع سريعة يمكنها تحسين تعليمات JavaScript وTypeScript وJSX وCSS. ستساعدك هذه المقالة على سرعة استخدام esbuild وستوضح لك كيفية إنشاء نظام البناء الخاص بك دون تبعيات أخرى.

جدول المحتويات
  1. كيف يعمل esbuild؟
  2. لماذا الحزمة؟
  3. لماذا استخدام esbuild؟
  4. لماذا تجنب esbuild؟
  5. بداية فائقة السرعة
  6. مشروع مثال
  7. نظرة عامة على المشروع
  8. تكوين esbuild
  9. حزمة جافا سكريبت
  10. تجميعة CSS
  11. المشاهدة وإعادة البناء والخدمة
  12. نبذة عامة

كيف يعمل esbuild؟

أطر مثل برغي لقد اعتمدت esbuild، ولكن يمكنك استخدام esbuild كأداة مستقلة في مشاريعك الخاصة.

  • esbuild حزم كود جافا سكريبت في ملف واحد بطريقة مشابهة للمجمعات مثل التراكمي. هذه هي الوظيفة الأساسية لـ esbuild، وهي تعمل على حل الوحدات النمطية، والإبلاغ عن مشكلات بناء الجملة، و"اهتزاز الشجرة" لإزالة الوظائف غير المستخدمة، ومسح بيانات التسجيل ومصحح الأخطاء، وتقليل التعليمات البرمجية، وتوفير خرائط المصدر.

  • إنشاء حزم كود CSS في ملف واحد. إنه ليس بديلاً كاملاً للمعالجات المسبقة مثل ساس or بوستCSS، ولكن يمكن لـ esbuild التعامل مع الأجزاء الجزئية، ومشكلات بناء الجملة، والتداخل، وترميز الأصول المضمنة، وخرائط المصدر، والبادئة التلقائية، والتصغير. قد يكون هذا كل ما تحتاجه.

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

سيساعدك الكود أدناه على فهم مفاهيم esbuild حتى تتمكن من استكشاف المزيد من فرص التكوين لمشاريعك.

لماذا الحزمة؟

يوفر تجميع التعليمات البرمجية في ملف واحد فوائد متنوعة. وهنا بعض منها:

  • يمكنك تطوير ملفات مصدر أصغر حجمًا ومكتفية ذاتيًا يسهل صيانتها
  • يمكنك فحص التعليمات البرمجية وتجميلها والتحقق من تركيبها أثناء عملية التجميع
  • يمكن للمجمع إزالة الوظائف غير المستخدمة - المعروفة باسم هز الشجرة
  • يمكنك تجميع إصدارات بديلة من نفس الكود وإنشاء أهداف للمتصفحات الأقدم مثل Node.js وDeno وما إلى ذلك.
  • يتم تحميل الملفات الفردية بشكل أسرع من الملفات المتعددة ولا يتطلب المتصفح دعم وحدة ES
  • يمكن للتجميع على مستوى الإنتاج تحسين الأداء عن طريق تصغير التعليمات البرمجية وإزالة بيانات التسجيل وتصحيح الأخطاء

لماذا استخدام esbuild؟

على عكس حزم JavaScript، فإن esbuild عبارة عن برنامج Go قابل للتنفيذ والذي ينفذ معالجة متوازية ثقيلة. إنه سريع وأسرع بما يصل إلى مائة مرة من Rollup أو Parcel أو Webpack. يمكن أن يوفر أسابيع من وقت التطوير طوال عمر المشروع.

بالإضافة إلى ذلك، يقدم esbuild أيضًا:

  • تجميع وتجميع مدمج لـ JavaScript وTypeScript وJSX وCSS
  • واجهات برمجة تطبيقات تكوين سطر الأوامر وجافا سكريبت وGo
  • دعم وحدات ES وCommonJS
  • خادم تطوير محلي مع وضع المراقبة وإعادة التحميل المباشر
  • الإضافات لإضافة المزيد من الوظائف
  • توثيق شامل و أداة التجريب عبر الإنترنت

لماذا تجنب esbuild؟

في وقت كتابة هذا التقرير، وصل esbuild إلى الإصدار 0.18. إنه منتج موثوق به ولكنه لا يزال منتجًا تجريبيًا.

يتم تحديث esbuild بشكل متكرر وقد تتغير الخيارات بين الإصدارات. ال توثيق توصي بالالتزام بإصدار محدد. يمكنك تحديثه، ولكن قد تحتاج إلى ترحيل ملفات التكوين الخاصة بك والتعمق في الوثائق الجديدة لاكتشاف التغييرات العاجلة.

لاحظ أيضًا أن esbuild لا يقوم بالتحقق من نوع TypeScript، لذا ستظل بحاجة إلى التشغيل tsc -noEmit.

بداية فائقة السرعة

إذا لزم الأمر، قم بإنشاء مشروع Node.js جديد باستخدام npm init، ثم قم بتثبيت esbuild محليًا باعتباره تابعًا للتطوير:

npm install esbuild --save-dev --save-exact

يتطلب التثبيت حوالي 9 ميغابايت. تحقق من أنه يعمل عن طريق تشغيل هذا الأمر لرؤية الإصدار المثبت:

./node_modules/.bin/esbuild --version

أو قم بتشغيل هذا الأمر لعرض تعليمات CLI:

./node_modules/.bin/esbuild --help

استخدم واجهة برمجة تطبيقات CLI لتجميع برنامج نصي للإدخال (myapp.js) وجميع الوحدات المستوردة في ملف واحد اسمه bundle.js. سيقوم esbuild بإخراج ملف باستخدام تنسيق التعبير الوظيفي الافتراضي (IIFE) الذي يستهدف المستعرض والذي يتم استدعاؤه على الفور:

./node_modules/.bin/esbuild myapp.js --bundle --outfile=bundle.js

اطلع على تثبيت esbuild بطرق أخرى إذا كنت لا تستخدم Node.js.

مشروع مثال

قم بتنزيل ملفات الأمثلة و تكوين esbuild من جيثب. إنه مشروع Node.js، لذا قم بتثبيت تبعية esbuild الفردية باستخدام:

npm install

بناء الملفات المصدر في src ل build الدليل وابدأ خادم التطوير باستخدام:

npm start

انتقل الآن إلى localhost:8000 في متصفحك لعرض صفحة ويب تعرض ساعة الوقت الفعلي. عند تحديث أي ملف CSS في src/css/ or src/css/partials، سيقوم esbuild بإعادة تجميع التعليمات البرمجية وإعادة تحميل الأنماط مباشرة.

مثال لبناء مشروع الساعة

صحافة السيطرة|كمد + السيطرة|كمد لإيقاف الخادم.

قم بإنشاء إصدار إنتاج للنشر باستخدام:

npm run build

افحص ملفات CSS وJavaScript في ملف build الدليل لرؤية الإصدارات المصغرة بدون خرائط المصدر.

نظرة عامة على المشروع

تم إنشاء صفحة الساعة في الوقت الفعلي في ملف build الدليل باستخدام الملفات المصدر من src.

package.json يحدد الملف خمسة npm نصوص. الأول يحذف build دليل:

"clean": "rm -rf ./build",

قبل حدوث أي تجميع، أ init يعمل البرنامج النصي clean، يخلق جديد build الدليل والنسخ:

  1. ملف HTML ثابت من src/html/index.html إلى build/index.html
  2. صور ثابتة من src/images/ إلى build/images/
"init": "npm run clean && mkdir ./build && cp ./src/html/* ./build/ && cp -r ./src/images ./build",

An esbuild.config.js يتحكم الملف في عملية تجميع esbuild باستخدام JavaScript API. يعد هذا أسهل في الإدارة من تمرير الخيارات إلى واجهة برمجة تطبيقات CLI، والتي يمكن أن تصبح غير عملية. ان npm bundle يعمل البرنامج النصي init تليها node ./esbuild.config.js:

"bundle": "npm run init && node ./esbuild.config.js",

الاخيرتين npm تشغيل البرامج النصية bundle مع إما أ production or development تم تمرير المعلمة إلى ./esbuild.config.js للتحكم في البناء:

"build": "npm run bundle -- production",
"start": "npm run bundle -- development"

متى ./esbuild.config.js يتم تشغيله، فهو يحدد ما إذا كان ينبغي إنشاءه بشكل مصغر production الملفات (الافتراضي) أو development الملفات ذات التحديثات التلقائية وخرائط المصدر وخادم إعادة التحميل المباشر. في كلتا الحالتين، قم ببناء الحزم:

  • ملف الدخول CSS src/css/main.css إلى build/css/main.css
  • ملف جافا سكريبت الإدخال scr/js/main.js إلى build/js/main.js

تكوين esbuild

package.json لديها "type" of "module" لذلك كل .js يمكن للملفات استخدام وحدات ES. ال esbuild.config.js واردات البرنامج النصي esbuild ومجموعات productionMode إلى true عند التجميع للإنتاج أو false عند التجميع من أجل التطوير:

import { argv } from 'node:process';
import * as esbuild from 'esbuild'; const productionMode = ('development' !== (argv[2] || process.env.NODE_ENV)), target = 'chrome100,firefox100,safari15'.split(','); console.log(`${ productionMode ? 'production' : 'development' } build`);

هدف الحزمة

نلاحظ أن متغير الهدف يحدد مجموعة من المتصفحات وأرقام الإصدارات لاستخدامها في التكوين. يؤثر هذا على المخرجات المجمعة ويغير البنية لدعم منصات معينة. على سبيل المثال، يمكن لـ esbuild:

  • توسيع تداخل CSS الأصلي في محددات كاملة (سيبقى التداخل إذا "Chrome115" كان الهدف الوحيد)
  • أضف خصائص CSS مسبوقة بالبائع عند الضرورة
  • polyfill ال ?? عامل الاندماج الباطل
  • إزالة # من مجالات الطبقة الخاصة

بالإضافة إلى المتصفحات، يمكنك أيضًا استهدافها node و es إصدارات مثل es2020 و esnext (أحدث ميزات JS وCSS).

حزمة جافا سكريبت

أبسط واجهة برمجة تطبيقات لإنشاء حزمة:

await esbuild.build({ entryPoints: ['myapp.js'], bundle: true outfile: 'bundle.js'
});

يؤدي هذا إلى تكرار أمر CLI المستخدم أعلاه:

./node_modules/.bin/esbuild myapp.js --bundle --outfile=bundle.js

يستخدم مشروع المثال خيارات أكثر تقدمًا مثل مشاهدة الملفات. وهذا يتطلب بناء طويل الأمد سياق الكلام الذي يحدد التكوين:


const buildJS = await esbuild.context({ entryPoints: [ './src/js/main.js' ], format: 'esm', bundle: true, target, drop: productionMode ? ['debugger', 'console'] : [], logLevel: productionMode ? 'error' : 'info', minify: productionMode, sourcemap: !productionMode && 'linked', outdir: './build/js' });

عروض البناء العشرات من خيارات التكوين. فيما يلي ملخص لتلك المستخدمة هنا:

  • entryPoints يحدد مجموعة من نقاط إدخال الملفات للتجميع. يحتوي المشروع المثال على برنامج نصي واحد في ./src/js/main.js.

  • format يضبط تنسيق الإخراج. يستخدم المثال esm، ولكن يمكنك تعيينها بشكل اختياري iife للمتصفحات القديمة أو commonjs لـ Node.js.

  • bundle تعيين إلى true تضمين الوحدات المستوردة في ملف الإخراج.

  • target هي مجموعة من المتصفحات المستهدفة المحددة أعلاه.

  • drop مجموعة من console و / أو debugger تصريحات لإزالة. في هذه الحالة، تقوم إصدارات الإنتاج بإزالة كليهما وتحتفظ إصدارات التطوير بهما.

  • logLevel يحدد إسهاب التسجيل. يوضح المثال أعلاه الأخطاء أثناء إنشاءات الإنتاج والمزيد من رسائل المعلومات المطولة أثناء إنشاءات التطوير.

  • minify يقلل حجم الكود عن طريق إزالة التعليقات والمسافات البيضاء وإعادة تسمية المتغيرات والوظائف حيثما أمكن ذلك. يتم تصغير مشروع المثال أثناء إنشاءات الإنتاج ولكن يجمل الكود أثناء بناء التطوير.

  • sourcemap تعيين إلى linked (في وضع التطوير فقط) ينشئ خريطة مصدر مرتبطة في ملف .map الملف بحيث يتوفر الملف المصدر الأصلي والخط في أدوات مطور المتصفح. يمكنك أيضًا ضبط inline لتضمين الخريطة المصدر داخل الملف المجمع، both لإنشاء كليهما، أو external لتوليد .map الملف بدون رابط من JavaScript المجمعة.

  • outdir يحدد دليل إخراج الملفات المجمعة.

استدعاء كائن السياق rebuild() طريقة لتشغيل البناء مرة واحدة - عادةً لبناء الإنتاج:

await buildJS.rebuild();
buildJS.dispose(); 

استدعاء كائن السياق watch() طريقة لمواصلة التشغيل وإعادة الإنشاء تلقائيًا عند تغيير الملفات التي تمت مشاهدتها:

await buildJS.watch();

يضمن كائن السياق معالجة الإصدارات اللاحقة بشكل متزايد وإعادة استخدام العمل من الإصدارات السابقة لتحسين الأداء.

جافا سكريبت ملفات الإدخال والإخراج

الدخول src/js/main.js واردات الملف dom.js و time.js وحدات من lib المجلد الفرعي. يجد كافة العناصر مع فئة من clock ويضبط محتوى النص الخاص بهم على الوقت الحالي كل ثانية:

import * as dom from './lib/dom.js';
import { formatHMS } from './lib/time.js'; const clock = dom.getAll('.clock'); if (clock.length) { console.log('initializing clock'); setInterval(() => { clock.forEach(c => c.textContent = formatHMS()); }, 1000); }

dom.js تصدير وظيفتين. main.js يستورد كلاهما ولكن يستخدم فقط getAll():

 export function get(selector, doc = document) { return doc.querySelector(selector);
} export function getAll(selector, doc = document) { return Array.from(doc.querySelectorAll(selector));
}

time.js تصدير وظيفتين. main.js واردات formatHMS()، ولكن هذا يستخدم الوظائف الأخرى في الوحدة:

 function timePad(n) { return String(n).padStart(2, '0');
} export function formatHM(d = new Date()) { return timePad(d.getHours()) + ':' + timePad(d.getMinutes());
} export function formatHMS(d = new Date()) { return formatHM(d) + ':' + timePad(d.getSeconds());
}

تتم إزالة حزمة التطوير الناتجة (اهتزاز الشجرة) get() تبدأ من dom.js ولكن يشمل كل time.js المهام. يتم أيضًا إنشاء خريطة المصدر:


function getAll(selector, doc = document) { return Array.from(doc.querySelectorAll(selector));
} function timePad(n) { return String(n).padStart(2, "0");
} function formatHM(d = new Date()) { return timePad(d.getHours()) + ":" + timePad(d.getMinutes());
} function formatHMS(d = new Date()) { return formatHM(d) + ":" + timePad(d.getSeconds());
} var clock = getAll(".clock");
if (clock.length) { console.log("initializing clock"); setInterval(() => { clock.forEach((c) => c.textContent = formatHMS()); }, 1e3);
} 

(لاحظ أن esbuild يمكن إعادة كتابة let و const إلى var من أجل الصحة والسرعة.)

تعمل حزمة الإنتاج الناتجة على تصغير الكود إلى 322 حرفًا:

function o(t,c=document){return Array.from(c.querySelectorAll(t))}function e(t){return String(t).padStart(2,"0")}function l(t=new Date){return e(t.getHours())+":"+e(t.getMinutes())}function r(t=new Date){return l(t)+":"+e(t.getSeconds())}var n=o(".clock");n.length&&setInterval(()=>{n.forEach(t=>t.textContent=r())},1e3);

تجميعة CSS

تستخدم تجميعة CSS في مشروع المثال كائن سياق مشابهًا لـ JavaScript أعلاه:


const buildCSS = await esbuild.context({ entryPoints: [ './src/css/main.css' ], bundle: true, target, external: ['/images/*'], loader: { '.png': 'file', '.jpg': 'file', '.svg': 'dataurl' }, logLevel: productionMode ? 'error' : 'info', minify: productionMode, sourcemap: !productionMode && 'linked', outdir: './build/css' });

يحدد ملف external الخيار كمجموعة من الملفات والمسارات إلى منع من البناء. في المشروع المثال، الملفات الموجودة في src/images/ يتم نسخ الدليل إلى build الدليل حتى يتمكن HTML أو CSS أو JavaScript من الرجوع إليها مباشرةً. إذا لم يتم تعيين هذا، فسيقوم esbuild بنسخ الملفات إلى الإخراج build/css/ الدليل عند استخدامها في background-image أو خصائص مماثلة.

loader يغير الخيار كيفية تعامل esbuild مع ملف مستورد لم تتم الإشارة إليه كملف external أصل. في هذا المثال:

  • تصبح صور SVG مضمنة كمعرفات URI للبيانات
  • يتم نسخ صور PNG وJPG إلى الملف build/css/ الدليل والمشار إليه كملفات

ملفات الإدخال والإخراج CSS

الدخول src/css/main.css واردات الملف variables.css و elements.css من partials المجلد الفرعي:


@import './partials/variables.css';
@import './partials/elements.css';

variables.css يحدد الخصائص المخصصة الافتراضية:


:root { --font-body: sans-serif; --color-fore: #fff; --color-back: #112;
}

elements.css يحدد جميع الأنماط. ملحوظة:

  • ال body لديه صورة خلفية محملة من الخارج images دليل
  • ال h1 متداخلة في الداخل header
  • ال h1 يحتوي على خلفية SVG والتي سيتم تضمينها
  • المتصفحات المستهدفة لا تتطلب أي بادئات البائع

*, *::before, ::after { box-sizing: border-box; font-weight: normal; padding: 0; margin: 0;
} body { font-family: var(--font-body); color: var(--color-fore); background: var(--color-back) url(/images/web.png) repeat; margin: 1em;
} header { & h1 { font-size: 2em; padding-left: 1.5em; margin: 0.5em 0; background: url(../../icons/clock.svg) no-repeat; } } .clock { display: block; font-size: 5em; text-align: center; font-variant-numeric: tabular-nums;
}

تقوم حزمة التطوير الناتجة بتوسيع بناء الجملة المتداخل، وتضمين SVG، وإنشاء خريطة مصدر:


:root { --font-body: sans-serif; --color-fore: #fff; --color-back: #112;
} *,
*::before,
::after { box-sizing: border-box; font-weight: normal; padding: 0; margin: 0;
}
body { font-family: var(--font-body); color: var(--color-fore); background: var(--color-back) url(/images/web.png) repeat; margin: 1em;
}
header h1 { font-size: 2em; padding-left: 1.5em; margin: 0.5em 0; background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><style>*{fill:none;stroke:%23fff;stroke-width:1.5;stroke-miterlimit:10}</style></defs><circle cx="12" cy="12" r="10.5"></circle><circle cx="12" cy="12" r="0.95"></circle><polyline points="12 4.36 12 12 16.77 16.77"></polyline></svg>') no-repeat;
}
.clock { display: block; font-size: 5em; text-align: center; font-variant-numeric: tabular-nums;
} 

تعمل حزمة الإنتاج الناتجة على تصغير الكود إلى 764 حرفًا (تم حذف SVG هنا):

:root{--font-body: sans-serif;--color-fore: #fff;--color-back: #112}*,*:before,:after{box-sizing:border-box;font-weight:400;padding:0;margin:0}body{font-family:var(--font-body);color:var(--color-fore);background:var(--color-back) url(/images/web.png) repeat;margin:1em}header h1{font-size:2em;padding-left:1.5em;margin:.5em 0;background:url('data:image/svg+xml,<svg...></svg>') no-repeat}.clock{display:block;font-size:5em;text-align:center;font-variant-numeric:tabular-nums}

المشاهدة وإعادة البناء والخدمة

ما تبقى من esbuild.config.js يتم إنشاء حزم البرامج النصية مرة واحدة للإنتاج قبل الإنهاء:

if (productionMode) { await buildCSS.rebuild(); buildCSS.dispose(); await buildJS.rebuild(); buildJS.dispose(); }

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

else { await buildCSS.watch(); await buildJS.watch(); await buildCSS.serve({ servedir: './build' }); }

ابدأ بناء التطوير باستخدام:

npm start

ثم انتقل إلى localhost:8000 لعرض الصفحة.

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

new EventSource('/esbuild').addEventListener('change', () => location.reload());

يستخدم مشروع المثال كائن سياق CSS لإنشاء الخادم. وذلك لأنني أفضل تحديث تغييرات JavaScript يدويًا — ولأنني لم أتمكن من إيجاد طريقة لـ esbuild لإرسال حدث لتحديثات CSS وJS! تتضمن صفحة HTML البرنامج النصي التالي لاستبدال ملفات CSS المحدثة دون تحديث الصفحة بالكامل (إعادة التحميل السريع):

<script type="module">
// esbuild server-sent event - live reload CSS
new EventSource('/esbuild').addEventListener('change', e => { const { added, removed, updated } = JSON.parse(e.data); // reload when CSS files are added or removed if (added.length || removed.length) { location.reload(); return; } // replace updated CSS files Array.from(document.getElementsByTagName('link')).forEach(link => { const url = new URL(link.href), path = url.pathname; if (updated.includes(path) && url.host === location.host) { const css = link.cloneNode(); css.onload = () => link.remove(); css.href = `${ path }?${ +new Date() }`; link.after(css); } }) });

لاحظ أن esbuild لا يدعم حاليًا إعادة التحميل السريع لـ JavaScript — لا يعني ذلك أنني أثق به على أي حال!

نبذة عامة

مع القليل من التكوين، يمكن أن يكون esbuild كافيًا للتعامل مع جميع متطلبات تطوير مشروعك وبناء الإنتاج.

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

لقد تم استخدام esbuild لمدة عام. السرعة مذهلة مقارنة ببرامج الحزم المماثلة، وتظهر الميزات الجديدة بشكل متكرر. الجانب السلبي الوحيد هو كسر التغييرات التي تتطلب الصيانة.

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

بقعة_صورة

أحدث المعلومات الاستخباراتية

بقعة_صورة