Appearance
ماژولها (Module)
نسخهی ویدئویی
نسخهی ویدئویی این آموزش را میتوانید از طریق لینکهای زیر دریافت کنید. البته توصیهی ما مطالعهی نسخهی متنی آن است.
بخش اوّل: https://cdn.gaplication.com/o/e3c5302b840a452f823510fb1e825d5b
بخش دوم: https://cdn.gaplication.com/o/46f6858ec78a4018bcfad203e11c97a2
سیستم Common.js
زمانی که Node.js متولد شد هنوز خبری از EcmaScript 2015 و سیستم ماژول مبتنی بر import
و export
نبود. امّا Node.js به عنوان ابزاری برای اجرای جاوااسکریپت به صورت مستقل از مرورگر باید به فکر سیستمی غیر از تگ <script>
برای بارگذاری و اجرای فایلهای جاوااسکریپت میبود. از این رو سیستم ماژول Common.js (یا CJS) متولّد شد.
نکته
امروزه با وجود پشتیبانی Node.js از استاندارد رسمی ماژولهای اکمااسکریپت (یعنی همان import و export) همچنان بسیاری از پکیجهای Node مبتنی بر Common.js هستند، از این رو لازم است با سیستم Common.js و ویژگیهای آن آشنا باشید.
فرض کنید فایلی داریم تحت عنوان math.js که تابعی درون آن ساختهایم.
js
// math.js
function add(a, b) {
return a + b
}
حالا میخواهیم در فایل دیگری که مثلا app.js نام دارد از آن استفاده کنیم.
js
// app.js
const result = add(2, 3)
console.log(result)
ولی متأسفانه از آنجایی که تابع add
گلوبال نیست نمیتوانید به همین سادگی از آن داخل یک فایل دیگر استفاده کنید.
طبق ساختار Common.js باید ابتدا در فایل اول چیزهایی را که میخواهیم در سایر فایلها استفاده کنیم به یک شیء که exports
نام دارد و از قبل موجود است بچسبانیم:
js
// math.js
function add(a, b) {
return a + b
}
exports.add = add
سپس با استفاده از تابع require
به کل این شیء exports
دسترسی پیدا کنیم:
js
// app.js
const math = require('./math')
const result = math.add(2, 3)
console.log(result)
حالا اگر با دستور node ./app
فایل app.js را اجرا کنید خواهید دید که بدون مشکل خاصی عدد 5 نمایش داده خواهد شد.
تابع require
به عنوان ورودی آدرس فایلی را که قرار است بخوانیم به صورت نسبی (نسبت به فایل فعلی) میگیرد. در مثال بالا فایل math.js که در کنار app.js درون یک پوشه بودند به صورت require('./math')
آدرسدهی شده است، زیرا طبق قانون Common.js حتی اگر پکیجها درون یک پوشه در کنار همدیگر هم باشند باید با '.'
به همان پوشه فعلی اشاره کنیم و require('math')
معنای دیگری دارد (به معنای خواندن یک پکیج با نام math است). نکته دوم اینکه در هنگام آدرسدهی با تابع require نیازی به نوشتن پسوند js نیست.
اگر آدرسی که به تابع require میدهیم نسبی نباشد (یعنی با .
شروع نشود) به معنای آدرسدهی به یک پکیج است. مثلا اگر پکیجی با نام axios نصب کردهاید و میخواهید از توابع آن در فایل خود استفاده کنید به این صورت مینویسید:
js
const axios = require('axios')
با این کار درون پوشهی node_modules
دنبال پوشهی axios
میگردد، سپس فایل package.json
آن را میخواند و از طریق فیلد "main"
آدرس فایل اصلی پکیج را که باید require
شود به دست میآورد.
سیستم ماژول اکمااسکریپت (ESM)
بعد از ظهور اکمااسکریپت ۲۰۱۵ سیستم ماژول رسمی اکمااسکریپت (EcmaScript Module یا ESM) معرفی شد، البته چند سال طول کشید تا به طور کامل در مرورگرها و Node.js پشتیبانی شود.
نمونهی ESM مثالی که در بالا گفتیم به این شکل میشود:
ابتدا تابعی را که میخواهیم در سایر فایلها استفاده شود export میکنیم:
js
// math.js
export function add(a, b) {
return a + b
}
سپس با استفاده از دستور import
به همان چیزهایی که export کردیم دسترسی پیدا میکنیم:
js
// app.js
import { add } from './math'
const result = add(2, 3)
console.log(result)
دستور import باید بالای تمام دستورات دیگر نوشته شود، و غیر از Comment چیز دیگری نباید قبل آن بنویسید.
در سیستم ماژول اکمااسکریپت مسیری که import میکنیم یا باید به صورت نسبی باشد و یا URL فایل جاوااسکریپت مورد نظر برای import باشد. ضمناً در این سیستم باید نام فایل به صورت کامل (همراه با پسوند) نوشته شود.
نکته
در ESM مسیردهیِ مطلق مجاز نیست. مگر اینکه با استفاده از مفهومی تحت عنوان import map دقیقاً تعریف کنیم که هر اسم مطلق به چه چیزی اشاره میکند.
Default import
هر ماژول در سیستم ESM میتواند دارای یک import پیشفرض باشد. استفاده از آن به این صورت است:
js
// file-1.js
export default 56
js
// file-2.js
import data from './file-1.js'
console.log(data) // 5
در import پیشفرض نیازی به استفاده از { ... }
نیست، و نام متغیری که جلوی import مینویسید هر چیز دلخواهی میتواند باشد.
Dynamic import
در حالت عادی وقتی فایلی را import میکنیم مرورگر ابتدا آن را دانلود کرده و سپس مابقی کد را اجرا میکند. این باعث کندی بارگذاری صفحه میشود. یکی از قابلیتهای سیستم ماژول اکمااسکریپت دانلود ماژول مورد نظر در زمان لازم است. برای این کار به جای import عادی از dynamic import استفاده میکنیم به این صورت:
js
button.addEventListener('click', async e => {
const module = await import('./read-qr-code.js')
module.read()
})
به این شکل فایل مورد نظر تنها هنگام کلیک روی دکمه دانلود و استفاده میشود، بنابراین حجم آن روی سرعت بارگذاری اولیه صفحه تأثیر منفی نمیگذارد.
ESM در مرورگر:
قبل از ظهور سیستمهای ماژول متغیرهایی که درون هیچ تابع یا بلاک کدی نبودند (اصطلاحا top-level بودند) گلوبال میشدند، ولی در سیستم ESM به همان فایل محدود شده و از طریق import و export در سایر فایلها استفاده میشوند. بنابراین سیستم ESM رفتار قدیمی کدها را تغییر میداد، از طرفی قابلیتهای جدید جاوااسکریپت با این دیدگاه که نباید کدهایی که قبلا نوشته شده است خراب شود به این زبان اضافه میشود. به همین دلیل برای استفاده از ESM باید امکانی تعبیه میشد که صراحتا به مرورگر بگوییم کد ما از سیستم ماژول اکمااسکریپت استفاده میکند. برای این کار باید type تگ script را برابر module قرار دهیم:
html
<script src="app.js" type="module"></script>
با این کار فایل js ما به عنوان یک ماژول اکمااسکریپت شناسایی میشود و میتوان در آن از import و export استفاده کرد.
علاوه بر آن صفت nomodule نیز به تگ script اضافه شد، اگر تگ script دارای این صفت باشد مرورگرهای جدید آن را اجرا نمیکنند. منظور از مرورگرهای جدید در اینجا مرورگرهایی است که از ESM پشتیبانی میکنند.
html
<script src="new.js" type="module"></script>
<script src="old.js" nomodule></script>
مرورگرهای قدیمی type="module"
را به عنوان یک type معتبر نمیشناسند و اصلا اسکریپت اول را اجرا نمیکنند، از طرفی صفت nomodule روی تگ اسکریپت دوم را زائد در نظر میگیرند و آن را اجرا میکنند.
بنابراین فایل new.js فقط در مرورگرهای جدید و فایل old.js فقط در مرورگرهای قدیمی اجرا میشود.
ESM در Node.js
در Node.js برای استفاده از ESM باید درون packge.json مقدار فیلد type را برابر module قرار دهیم. با این کار باید همهی فایلهای js عادی را در ساختار import و export بنویسیم.
در استاندارد Node.js اگر پسوند یک فایل جاوااسکریپت mjs باشد به این معناست که این فایل از ساختار ماژول اکمااسکریپت استفاده میکند (m در ابتدای mjs کوتاهشدهی module است)، بدون توجه به اینکه type کل پکیج چیست. همچنین اگر پسوند فایلی cjs باشد یعنی از ساختار CommonJS استفاده میکند بدون توجه به type کل پکیج.
در Node.js میتوان بدون هیچ مشکلی فایلهای Common.js را درون ماژولهای اکمااسکریپت import کرد. امّا نمیتوانیم در یک فایل Common.js فایل ESM را require کنیم! تنها روش استفاده از ESM در Common.js استفاده از Dynamic import است.