اگر در حوزه توسعه وب کار کرده باشید حتما با package bundler ها آشنا هستید و حتما نام معروف ترین آن ها webpack را شنیده اید اما معایبی مانند سنگین بودن بیش از حد webpack وجود دارد که بسیاری از افراد را از استفاده از آن ناراضی کرده است. ما در این مقاله می خواهیم به معرفی و آموزش یک bundler مشهور دیگر به نام parcel (پارسل) بپردازیم که از نظر سرعت عملکرد بسیار بهتری نسبت به webpack دارد.
Parcel یک کامپایلر برای تمام کدهای شما حساب می شود و اصلا اهمیتی ندارد که کدهایتان را به چه زبانی نوشته باشید. کار اصلی پارسل این است که کد شما و تمام وابستگی های آن را بگیرد و سپس آن ها را به یک یا چند فایل نهایی تبدیل کند. فرآیند کامپایل شدن کدها در پارسل به صورت موازی و چند هسته ای انجام می شود بنابراین سرعت آن بسیار عالی است.
پارسل یک bundler است که یعنی فایل های مختلف شما را در یک یا چند فایل محدود ادغام می کند. این مسئله باعث می شود تعداد درخواست های HTTP به سرور شما بسیار کمتر شود. همچنین فایل های جاوا اسکریپت و CSS به صورت خودکار minify می شوند بنابراین حجم فایل هایتان کاهش پیدا می کند. فایل های توسعه نیز به صورت ماژولار مدیریت می شوند بنابراین کارتان بسیار راحت خواهد بود. در نهایت قابلیت بارگذاری on-demand (فقط زمانی که به کدها نیاز داریم) نیز در پارسل وجود دارد بنابراین سرعت برنامه شما را افزایش خواهد داد. طبیعتا پارسل مزیت های استفاده از bundler های دیگر را نیز دارد.
شما می توانید پارسل را به صورت سراسری روی کل سیستم خود نصب کنید اما تیم توسعه پارسل پیشنهاد می کنند به جای این کار پارسل را به صورت dev dependency روی هر پروژه به شکل جداگانه نصب کنید. این کار باعث می شود پروژه ها از یکدیگر جدا باشند و به مشکلات کمتری برخورد کنیم. برای نصب پارسل ابتدا یک پوشه به نام code (یا هر نام دلخواهی) ایجاد کنید و ترمینال خود را در آن مسیر باز کنید. حالا باید یک پروژه npm را در آن شروع کنیم:
npm init -y
با اجرای این دستور یک پروژه npm را آغاز کرده ایم. در مرحله بعدی باید دستور زیر را در ترمینال خود اجرا نمایید:
npm install -D parcel@next
با انجام این کار Parcel به عنوان dev dependency در پروژه شما نصب می شود. حالا باید چند اسکریپت را در package.json تعریف کنیم تا کار با پارسل را برای خودمان راحت تر کنیم:
{ "name": "code", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "parcel serve ./src/index.html", "build": "parcel build ./src/index.html", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "parcel": "^2.0.0-beta.3.1" } }
ما در فایل بالا دو اسکریپت تعریف کرده ایم:
یکی از تفاوت های اصلی در هر دو دستور این است که ما از یک فایل HTML به عنوان نقطه ورودی برنامه استفاده کرده ایم در حالی که نقطه ورودی در bundler های دیگر همیشه یک فایل جاوا اسکریپت است. در پوشه اصلی (code) یک پوشه دیگر به نام src ایجاد کرده و درون آن یک فایل به نام index.html تعریف کنید که محتوای اولیه ساده ای داشته باشد:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="./js/main.js" defer></script> <title>Parcel Demo</title> </head> <body> <main></main> </body> </html>
همانطور که می بینید من یک فایل جاوا اسکریپتی را به این صفحه اضافه کرده ام بنابراین در همین پوشه (src) یک پوشه دیگر به نام js ایجاد کرده و درون آن فایلی به نام main.js بسازید. من می خواهم محتویات این فایل بدین شکل باشد:
document.querySelector("main").innerHTML = "<p>This is a simple paragraph</p>";
ما با این کد عنصر main در صفحه را هدف گرفته و سپس یک پاراگراف ساده را در آن قرار داده ایم. حالا دستور npm start را در ترمینال خود اجرا کنید. با انجام این کار باید یک سرور توسعه در آدرس http://localhost:1234 برایتان باز شود و عبارت This is a simple paragraph را در صفحه مشاهده کنید.
عملیات transpilation یعنی تبدیل کدهای شما از یک نسخه از زبان به نسخه ای قدیمی تر از همان زبان (پکیج هایی مانند babel همین کار را انجام می دهند). Parcel به صورت پیش فرض هیچ transpilation ای انجام نمی دهد، یعنی اگر کدهای جاوا اسکریپتی خود را با ES7 بنویسید همان کدها در فایل باندل شده و نهایی وجود دارند. معمولا هدف اصلی از transpilation این است که کدها را به شکل جدید و با آخرین نسخه بنویسیم اما برای پشتیبانی از مرورگر های قدیمی آن ها را با یک برنامه مانند babel به کدهای ES5 یا قدیمی تر تبدیل کنیم.
در صورتی که می خواهید پارسل برایتان transpilation انجام بدهد باید از browserslist استفاده کنید. مثلا فرض کنید من می خواهم از Internet Explorer ورژن ۱۰ نیز پشتیبانی کنم (این کار اصلا پیشنهاد نمی شود و فقط یک مثال برای درک بهتر کدها است). در این حالت وارد فایل package.json شده و می گویم:
{ "name": "code", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "parcel serve ./src/index.html", "build": "parcel build ./src/index.html", "test": "echo \"Error: no test specified\" && exit 1" }, "browserslist": [ "IE 10" ], "keywords": [], "author": "", "license": "ISC", "devDependencies": { "parcel": "^2.0.0-beta.3.1" } }
با اضافه کردن کلید browserslist گفته ام که می خواهم تا مرورگر اینترنت اکسپلورر ۱۰ را نیز پشتیبانی کنم.
در پارسل زمانی که از target ها صحبت می کنیم منظور خاصی داریم. در هنگام اجرا شدن Parcel فایل های شما می توانند به چند شکل مختلف ساخته شوند که به هر کدام از آن ها یک target می گوییم. به طور مثال شما می توانید یک target برای هدف گرفتن مرورگر های جدید و یک target برای هدف گرفتن مرورگر های قدیمی تر داشته باشید. هر نقطه ورود (entry point) در پارسل به ازای هر target به صورت جداگانه پردازش خواهد شد.
اگر بخواهید برای خودتان یک target تعریف کنید باید ابتدا به فایل package.json برویم و خصوصیت targets را در آن تعریف کنیم. توجه داشته باشید که این فقط یک مثال برای درک ساختار کدها است:
{ "app": "dist/browser/index.js", "appModern": "dist/browserModern/index.js", "targets": { "app": { "engines": { "browsers": "> 0.25%" } }, "appModern": { "engines": { "browsers": "Chrome 90" } } } }
همانطور که می بینید من دو target را در بخش targets تعریف کرده ام:
خصوصیت engines موتور اصلی برنامه ما را مشخص می کند. مثلا آیا برنامه ما با node اجرا می شود (سمت سرور) یا با browsers (مرورگر - سمت کاربر) یا با electron (برنامه های دسکتاپ)؟ در مورد اول مرورگر هایی که کمی قدیمی تر هستند را پشتیبانی کرده ایم اما در مورد دوم (برنامه مدرن) از نسخه ۹۰ گوگل کروم به بالا را پشتیبانی می کنیم.
تقسیم کد به شما اجازه می دهد باندل نهایی برنامه خود را به قسمت های مختلفی تقسیم کنید و فقط زمانی که کاربر به بخش خاصی نیاز داشت، کدهای آن بخش را همانجا بارگذاری کنید. با انجام این کار دیگر نیازی به دانلود تمام کدها در همان بازدید اول از سایت نیست و طبیعتا سرعت بارگذاری بسیار بالاتر می رود. پارسل برای پیاده سازی این قابلیت از دستورات dynamic imports (بارگذاری پویا) استفاده می کند که ترکیبی از require و import هستند و یک promise را برمی گردانند.
برخلاف دستورات عادی import و require دستورات dynamic import می توانند در وسط کدهای شما باشند و به همین دلیل است که می توانند در لحظه نیاز بارگذاری شوند. فرض کنید ما یک فایل به نام about.js داشته باشیم که وظیفه اش نمایش صفحه about است:
export function render() { // نوشتن کدهایی که صفحه مورد نظر را نمایش می دهند }
ما می توانیم در یک فایل دیگر این فایل را به صورت پویا (دینامیک) بارگذاری کنیم:
import("./pages/about").then(function (page) { // نمایش صفحه page.render(); });
البته شما می توانید به جای then از ساختار جدید تر await نیز استفاده کنید:
async function load() { const page = await import("./pages/about"); // Render page page.render(); } load();
پارسل قابلیت هایی را دارد که از برخی قابلیت های Node.js تقلید می کنند. به طور مثال پارسل از dotenv برای خواندن متغیر های محیطی از فایل های env. استفاده می کند که بیشتر در پروژه های node دیده می شود.
همچنین زمانی که از پکیج هایی مانند crypto یا fs استفاده می کنید، پارسل به صورت خودکار از polyfills های تعریف شده در خودش استفاده کرده و یا اینکه آن ماژول خاص را به طور کل نادیده می گیرد. به طور مثال ماژول crypto به طور خودکار از crypto-browserify و ماژول https به صورت خودکار https-browserify استفاده می کند. مثلا اگر از fs.readFileSync استفاده کنید به صورت خودکار به محتوای آن فایل دسترسی خواهید داشت با اینکه پروژه شما اصلا node.js نیست. لیست این ماژول ها در وب سایت رسمی پارسل قرار دارد.
Parcel با این قابلیت می تواند کدهای بدون استفاده جاوا اسکریپت را حذف کند! اگر در حال استفاده از یک کتابخانه هستید و آن کتابخانه در فایل package.json خود خصوصیت sideEffects: false را مشخص کرده باشد، پارسل پردازش برخی از این منابع (مثلا transpile کردنشان) را به طور کامل نادیده گرفته و یا اصلا آن کدها را در بخش نهایی قرار نمی دهد.
به طور مثال فرض کنید فایلی به نام app.js داشته باشیم که چنین محتویاتی داشته باشد:
import { add } from "lib"; console.log(add(1, 2));
یعنی کتابخانه ای به نام lib را داریم که تابعی به نام add را به ما می دهد. ما از این تابع برای جمع کردن دو عدد ۱ و ۲ استفاده کرده ایم. از طرفی فایل node_modules/lib/package.json بدین شکل است:
{ "name": "lob" "sideEffects": false }
و در نهایت فایل node_modules/lib/index.js نیز بدین شکل است:
export { add } from "./add.js"; export { multiply } from "./multiply.js"; let loaded = Date.now(); export function elapsed() { return Date.now() - loaded; } let cache = new Map(); export function cached(param, func) { /* ... */ }
همانطور که از کد بالا مشخص می شود، این فایل فقط تابع add.js را دوباره export کرده است بنابراین اصلا نیازی نیست که کدهای موجود در این فایل را به باندل نهایی منتقل کنیم بلکه باید فقط تابع add را بگیریم و بقیه کدها را دور بیندازیم. مثلا هیچ نیازی به فایل node_modules/lib/multiply.js در پروژه ما نیست.
Parcel به شما کمک می کند تا باندل نهایی خودتان را بررسی کنید و اگر مشکلاتی را در آن دیدید اقدام به ویرایش کنید. در بخش قبل در رابطه با حذف کدهای اضافی جاوا اسکریپت صحبت کردیم اما این بخش مربوط به نگاهی کلی به گزارش هایی است که پارسل از باندل نهایی ارائه می دهد.
اولین قابلیت در این بخش Bundle Analyzer است که با استفاده از پلاگین parcel/reporter-bundle-analyzer@ انجام می شود. با استفاده از این پلاگین می توانید برای هر باندل یک فایل HTML ایجاد کنید:
parcel build src/index.html --reporter @parcel/reporter-bundle-analyzer
با اجرای این دستور یک پوشه به نام parcel-bundle-reports در پروژه شما ایجاد می شود که برای هر target یک فایل HTML دارد. هر کدام از این فایل های HTML گزارشی بصری از تمام وابستگی های پروژه تان ارائه می کنند:
همانطور که می بینید این یک گزارش بصری و کامل از باندل نهایی ما است.
قابلیت بعدی Bundle Buddy است که به جای تاکید بر جنبه بصری گزارش، بیشتر به جنبه کمی و تحلیل اعداد می پردازد. Bundle Buddy یک پکیج جداگانه است که با bundler های مختلفی کار می کند و پارسل یکی از آن ها است. گزارشات تولید شده توسط این پکیج تحلیل های مفید تری را برای شما دارند. برای تولید چنین گزارشی باید از دستور زیر استفاده کنید:
parcel build src/index.html --reporter @parcel/reporter-bundle-buddy
روش دیگر این است که به وب سایت Bundle Buddy رفته و فایل های درخواست شده را در آن بارگذاری کنید. این وب سایت فایل های شما را دریافت کرده و سپس آن ها را تحلیل می کند.
اشکال زدایی یا debug کردن برنامه های جاوا اسکریپتی معمولا با استفاده از Chrome developer tools انجام می شود. البته برای انجام این کار نیاز به فایل هایی به نام sourcemap داریم. این فایل ها در پارسل به صورت خودکار تولید می شوند. اگر دستوری مانند npm start را در ترمینال اجرا کنید، پوشه ای به نام dist در پوشه اصلی برنامه ایجاد می شود که حاوی فایل های کامپایل شده به همراه sourcemap ها می باشد. شما می توانید در کدهای خودتان breakpoint قرار بدهید و سپس کدها را اجرا کنید.
البته اگر دوست دارید فرآیند اشکال زدایی را در visual studio code انجام بدهید می توانید از افزونه Debugger for Chrome استفاده نمایید.
همانطور که توضیح دادم برای استفاده از جاوا اسکریپت نیازی به انجام هیچ عملیات اضافه ای ندارید. حتی برای انجام Transpilation فقط کافی است کلید browserslist در فایل package.json را تعریف کنید اما مسئله تایپ اسکریپت فرق می کند.
همانطور که می دانید مرورگر ها و Node تایپ اسکریپت را به صورت مستقیم اجرا نمی کنند بلکه باید کدهای تایپ اسکریپت را به جاوا اسکریپت تبدیل نمایید. خوشبختانه پارسل به صورت پیش فرض از تایپ اسکریپت پشتیبانی می کند اما چند گزینه مختلف برای انجام این کار دارید.
راه حل اول استفاده از پکیج Babel (به طور خاص babel/preset-typescript@) می باشد. این روش، روش پیش فرض در پارسل می باشد و تایپ های تایپ اسکریپت را از فایل هایتان حذف می کند. طبیعتا این روش سریع تر است اما معایبی دارد:
راه حل دوم استفاده از موتور اصلی تایپ اسکریپت یعنی tsc است. برای استفاده از این روش باید پلاگین parcel/transformer-typescript-tsc@ را نصب کنید تا از کامپایلر اصلی تایپ اسکریپت استفاده شود. بعد از آن باید به فایل پیکربندی پارسل (parcelrc.) رفته و این مسئله را به پارسل گوشزد کنید:
{ "extends": "@parcel/config-default", "transformers": { "*.{ts,tsx}": ["@parcel/transformer-typescript-tsc"] } }
البته این روش نیز تایپ های تایپ اسکریپت را به جای بررسی، حذف می کند بنابراین مشکلات خودش را دارد:
اگر می خواهید واقعا از type checking استفاده کنید باید ابتدا یک فایل tsconfig.json را داشته باشید که درون خودش خصوصیت include را دارد و به فایل های اصلی تایپ اسکریپت اشاره می کند. در مرحله بعدی به فایل parcelrc. رفته و validator تایپ اسکریپت را به آن اضافه می کنیم:
{ "extends": "@parcel/config-default", "validators": { "*.{ts,tsx}": ["@parcel/validator-typescript"] } }
توجه داشته باشید که در این روش چک کردن تایپ ها بعد از تولید باندل نهایی انجام می شود بنابراین اگر به خطایی برخورد کردید حتما بعد از تصحیح دوباره باندل را تولید کنید.
امیدوارم این مقاله به طور کلی شما را با قابلیت های Parcel آشنا کرده باشد.
منبع: وب سایت parceljs
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.