آشنایی با انواع ماژول های جاوا اسکریپت

Modules Introduction

26 خرداد 1400
آشنایی با انواع ماژول های جاوا اسکریپت

ماژول های جاوا اسکریپت

Module یا ماژول در لغت به معنی یک قطعه یا بخش است. زمانی که برنامه جاوا اسکریپتی ما بزرگتر و بزرگتر می شود، مجبور می شویم این برنامه را به فایل های مختلفی تقسیم کنیم و نمی توانیم تمام برنامه را در یک فایل بنویسیم. به هر کدام از این فایل ها یک ماژول می گوییم چرا که قطعه ای از کد ما را درون خودشان دارند (البته به شرطی که کدهایشان را export کنند). هر ماژول می تواند یک کلاس یا مجموعه ای از توابع یا برخی اطلاعات پایگاه داده یا ثابت ها و یا هر بخشی از کد را داشته باشد.

در سال های اولیه استفاده از جاوا اسکریپت هیچ سیستم ماژولی در جاوا اسکریپت وجود نداشت چرا که اسکریپت ها بسیار کوچک بودند و اصلا نیازی به تقسیم فایل ها وجود نداشت اما با گذر زمان جاوا اسکریپت آنچنان پیچیده شد که حالا در یک پروژه چندین کتابخانه مورد استفاده قرار می گیرد. توسعه دهندگان برای حل این مشکل شروع به استفاده از کتابخانه هایی کردند که این مشکلات را برطرف می کرد:

  • AMD: مخفف Asynchronous Module Definition است و یکی از قدیمی ترین و ساده ترین سیستم های ماژول موجود است که در ابتدا توسط کتابخانه require.js معرفی شد.
  • CommonJS: سیستم ماژولی که برای Node.js ساخته شده است.
  • UMD: یک سیستم ماژولی دیگر که با AMD و CommonJS سازگاری داشت.

با مرور زمان این سه ماژول به تاریخ می پیوندند و دیگر از آن ها استفاده نمی شود گرچه هنوز هم در برخی از کتابخانه ها وجود دارند. در نهایت ماژولی language-level (در سطح زبان) در سال ۲۰۱۵ معرفی شد که ES Modules یا ES6 Modules نام دارد و همان ماژول مدرن و امروزی در جاوا اسکریپت است. ما در طول این مقاله به بررسی و یادگیری این ماژول معتبر و مدرن می پردازیم و تنها در انتهای مقاله چند مثال از این ماژول های جاوا اسکریپت قدیمی برایتان قرار می دهم.

کار با یک ماژول جاوا اسکریپتی

همانطور که گفتم هر ماژول یک فایل جاوا اسکریپتی است که کدهایش را export می کند و هیچ مفهوم پیشرفته ای ندارد. شما می توانید در زبان جاوا اسکریپت با استفاده از دستورات import و export با ماژول ها کار کنید به صورتی که اولی برای وارد کردن یک ماژول در ماژول دیگر و دومی برای خروجی گرفتن از یک ماژول استفاده می شود. اگر بخواهیم به صورت فنی تر صحبت کنیم می گوییم:

  • export (به معنی صادر کردن): زمانی که این کلیدواژه روی توابع، متغیرها، کلاس ها و غیره اعمال شود، این توابع، متغیرها و کلاس ها از بیرون ماژول (از بیرون از فایل فعلی) قابل دسترس خواهند بود.
  • import (به معنی وارد کردن): این کلیدواژه به ما اجازه می دهد کدهای دیگر ماژول ها را به ماژول فعلی (فایل فعلی) وارد کنیم.

برای تمرین بیایید یک ماژول بسازیم. من یک فایل به نام sayHi.js می سازم که یک تابع ساده را export می کند:

// 📁 sayHi.js

export function sayHi(user) {

  alert(`Hello, ${user}!`);

}

همانطور که می بینید این تابع فقط یک alert را به کاربر نشان می دهد. حالا یک فایل دیگر به نام main.js را در کنار فایل قبلی می سازم و این تابع را در آن وارد می کنم:

// 📁 main.js

import {sayHi} from './sayHi.js';




alert(sayHi); // function...

sayHi('John'); // Hello, John!

همانطور که می بینید ما از آدرس دهی نسبی استفاده کرده ایم و گفته ام تابعی به نام sayHi از فایل sayHi.js وارد شود. حالا می توانیم به سادگی از آن استفاده کنیم، انگار که تعریف تابع sayHi در همین فایل موجود است! در نظر داشته باشید که در هنگام استفاده از ماژول ها باید یا از آدرس مطلق (absolute path) و یا از آدرس نسبی (relative path) استفاده کنید و استفاده از bare module ها مجاز نیست.

در صورتی که بخواهیم از این سیستم در یک مرورگر استفاده کنیم باید از همان تگ <script> استفاده کنیم با این تفاوت که تایپ آن از نوع module باشد:

<!doctype html>

<script type="module">

  import {sayHi} from './say.js';




  document.body.innerHTML = sayHi('John');

</script>

مرورگر به صورت خودکار ماژول را دریافت کرده و سپس اسکریپت را اجرا می کند. در ضمن اگر یک ماژول شما وابسته به ماژول های دیگری باشد (درونش import های دیگری داشته باشد) آن ها نیز به صورت خودکار وارد می شوند و نیازی نیست شما کاری انجام بدهید.

نکته: ماژول های جاوا اسکریپت فقط در پروتکل HTTP یا HTTPS کار می کنند بنابراین اگر پروژه خود  را با پروتکل //:file در سیستم خودتان باز کنید، با مشکل مواجه می شوید. برای توسعه محلی بهتر است از سرور های محلی (مثلا افزونه live server برای visual studio code) استفاده کنید.

اگر یادتان باشد در حالت قدیمی و هنگام کار با CommonJS از دستور require برای وارد کردن ماژول ها استفاده می کردیم:

const package = require('module-name')

اما در ES Modules ها دیگر خبری از require نیست و به جای آن به شکل زیر عمل می کنیم:

import package from 'module-name'

طبیعتا شما می توانید هر چیزی را که بخواهید از یک فایل export کنید. مثلا:

export default str => str.toUpperCase()

من تابع ساده بالا را export کرده ام اما توجه داشته باشید که این export از نوع default است؛ یعنی این ماژول به صورت پیش فرض تابع بالا را export می کند. حالا یک فایل دیگر می تواند آن را به شکل زیر import کند:

import toUpperCase from './uppercase.js'

toUpperCase('test') //'TEST'

زمانی که می خواهیم default export ها را import کنیم باید نامشان را به شکل بالا بنویسید و نیازی به نوشتن علامت های {} نیست.

همچنین در صورتی که می خواهید دستور export را برای تک تک متغیرها یا توابع یک ماژول قرار ندهید، می توانید در نهایت یک شیء را export کنید که حاوی تمام این مقادیر است:

const a = 1

const b = 2

const c = 3




export { a, b, c }

حالا اگر ماژولی بخواهد تمام این موارد را یکجا import کند باید از علامت * استفاده کند:

import * from 'module'

البته می توانید به جای وارد کردن همه آن ها فقط متغیرهایی که می خواهید را import کنید:

import { a } from 'module'

import { a, b } from 'module'

در ضمن می توانید با استفاده از دستور as نام یک import را تغییر بدهید. مثلا:

import { a, b as two } from 'module'

این کد یعنی a را به صورت عادی import کرده ایم اما b را پس از وارد کردن به two تغییر نام داده ایم. چرا چنین کاری را می کنیم؟ ممکن است شما از نام b خوشتان نیاید و یا اینکه متغیر دیگری به نام b در این فایل داشته باشید و الی آخر.

نهایتا سوالی پیش می آید که اگر یک ماژول هم default export و هم export های معمولی داشت، چطور از آن استفاده کنیم؟ در این حالت می توانید export ای که default می باشد را مانند قبل وارد کرده و سپس از export های معمولی استفاده کنید. توسعه دهندگان React معمولا با این موضوع آشنا هستند:

import React, { Component } from 'react'

در این مثال React همان default export است و Component یکی از export های عادی می باشد.

قابلیت های ماژول ها

تفاوت ماژول ها با اسکریپت های عادی چیست؟ آیا هر اسکریپتی یک ماژول است؟ اگر بخواهیم دقیق باشیم اینطور نیست! ما در این بخش می خواهیم تفاوت های ماژول ها با اسکریپت های عادی را توضیح بدهیم.

۱. استفاده از use strict

ماژول های جاوا اسکریپتی همیشه از use strict استفاده می کنند. در صورتی که نمی دانید، use strict باعث می شود کدهای جاوا اسکریپت در حالت strict اجرا شوند که حالتی سخت گیرانه تر و استاندارد تر از جاوا اسکریپت عادی است. به طور مثال در ماژول ها متغیرهایی که صریحا تعریف نشده باشند (undeclared variable - متغیر با const و let یا var تعریف نشده باشد) خطا می دهند:

<script type="module">

  a = 5; // error

</script>

۲. scope مستقل

ماژول های جاوا اسکریپتی دارای scope مستقلی هستند بنابراین یک متغیر در یک ماژول به صورت عادی در دسترس ماژول های دیگر نیست. به طور مثال من یک فایل به نام user.js تعریف می کنم و درون آن متغیری به نام user را تعریف می کنم:

let user = "John";

در مرحله بعدی یک فایل دیگر به نام hello.js ایجاد می کنم و سعی می کنم از این متغیر استفاده کنم:

alert(user); // no such variable (متغیرهای هر ماژول مستقل هستند)

حالا حتی اگر هر دو ماژول را در یک فایل HTML وارد کنیم، نمی توانیم از متغیری به نام user در فایل hello.js استفاده کنیم:

<!doctype html>

<script type="module" src="user.js"></script>

<script type="module" src="hello.js"></script>

اگر بخواهیم به متغیر user دسترسی داشته باشیم باید حتما آن را export کنیم و سپس در فایل hello.js وارد یا import کنیم. با این حساب برای تصحیح کدهای بالا ابتدا به user.js می رویم تا متغیر را export کنیم:

export let user = "John";

در مرحله بعدی به hello.js می رویم تا آن را import کنیم:

import {user} from './user.js';




document.body.innerHTML = user; // John

و به جای وارد کردن دو ماژول به صورت جداگانه، فقط ماژول اصلی (import کننده) را وارد فایل HTML می کنیم:

<!doctype html>

<script type="module" src="hello.js"></script>

در ضمن sope مستقل در مرورگر ها برای هر تگ script نیز وجود دارد:

<script type="module">

  // The variable is only visible in this module script

  let user = "John";

</script>




<script type="module">

  alert(user); // Error: user is not defined

</script>

در نهایت اگر نیاز دارید که متغیری به صورت سراسری در مرورگر وجود داشته باشد می توانید آن را به شیء window اضافه کنید تا مثلا به صورت window.user در دسترس باشد اما این روش فقط در شرایط خاص و استثناء استفاده می شود.

۳. اجرای کد ماژول ها فقط یک بار انجام می شود

اگر یک ماژول در چندین بخش مختلف import شود، کدهای درون آن فقط یک بار اجرا می شوند و سپس export ها به فایل های دیگر داده می شوند. این مسئله اهمیت زیادی دارد چرا که نتایج متفاوتی از اسکریپت های عادی دارد. به طور مثال اگر ماژول شما دارای side effect یا عارضه باشد (مثلا پیامی را به کاربر نمایش بدهد) import کردن آن در بخش های مختلف فقط باعث نمایش یک باره پیام می شود و اینطور نیست که پیام چندین بار به کاربر نمایش داده شود:

// 📁 alert.js

alert("Module is evaluated!");

ما این ماژول را ساخته ایم، حالا اگر آن را در بخش های مختلف وارد کنیم چه می شود؟

// وارد کردن ماژول در چند فایل مختلف




// 📁 1.js

import `./alert.js`; // کدهای ماژول اجرا می شود




// 📁 2.js

import `./alert.js`; // هیچ اتفاقی نمی افتد

اگر می خواستیم از تابع alert چندین بار استفاده کنیم باید آن را export می کردیم. بیایید به یک مثال پیشرفته تر نگاهی بیندازیم. فرض کنید ماژولی داشته باشیم که یک شیء را export می کند:

// 📁 admin.js

export let admin = {

  name: "John"

};

حالا اگر چند ماژول دیگر داشته باشیم که این ماژول را import می کنند، فقط import اول باعث می شود کدهای درون ماژول بالا اجرا شوند. این چه نتیجه ای دارد؟ یادتان باشد که اولین ماژولی که ماژول admin.js را صدا بزند باعث می شود کدهای درون آن اجرا شده و درخواست های export آن ساخته شود. این یعنی شیء admin.js فقط و فقط یک بار ساخته می شود بنابراین اگر یکی از ماژول ها آن را تغییر بدهد، دیگر ماژول ها نیز این تغییر را می بینند:

// 📁 1.js

import {admin} from './admin.js';

admin.name = "Pete";




// 📁 2.js

import {admin} from './admin.js';

alert(admin.name); // Pete




// با تغییر نام کاربر در یک فایل باعث تغییر آن در فایل های دیگر شده ایم

این رفتار باعث می شود که بتوانیم ماژول ها را پیکربندی کنیم. یعنی در ابتدا تنظیمات مورد نظرمان را تعیین کنیم و از آن پس در تمام بخش های دیگر برنامه از آن استفاده کنیم. به طور مثال ممکن است ماژول admin.js اصلا نام کاربر را تعیین نکند بلکه انتظار داشته باشد این نام از خارج بیاید:

// 📁 admin.js

export let admin = { };




export function sayHi() {

  alert(`Ready to serve, ${admin.name}!`);

}

طبیعتا شیء admin در کد بالا خصوصیتی به نام name ندارد. در مرحله بعد ما در ماژول init.js خصوصیت admin.name را تعریف می کنیم. با این کار name در دیگر ماژول ها و حتی در admin.js در دسترس خواهد بود:

// 📁 init.js

import {admin} from './admin.js';

admin.name = "Pete";

حالا در ماژول های دیگر چنین کدی را خواهیم داشت:

// 📁 other.js

import {admin, sayHi} from './admin.js';




alert(admin.name); // Pete




sayHi(); // Ready to serve, Pete!

۴. شیء meta

در ماژول ها شیء import.meta اطلاعاتی را راجع به ماژول فعلی در اختیار شما می گذارد. اطلاعاتی که این شیء برمی گرداند کاملا به محیط اجرایی آن وابسته است (در Node.js و روی یک سرور باشد یا در مرورگر باشد و غیره). در صورتی که در مرورگر باشیم URL کامل آن اسکریپت برایمان برگردانده می شود:

<script type="module">

  alert(import.meta.url); // script url (url of the html page for an inline script)

</script>

۵. در ماژول ها this تعریف نشده است

در ماژول ها کلیدواژه this تعریف نشده یا undefined می باشد در حالی که در محیط هایی مثل مرورگر، مقدار this برابر با window است. به مثال زیر توجه کنید:

<script>

  alert(this); // window

</script>




<script type="module">

  alert(this); // undefined

</script>

استفاده از ES Modules در Node.js

اگر با Node.js کار کرده باشید می دانید که node.js تا مدتی طولانی فقط از commonJS پشتیبانی می کرد و در دنیای مدرن باعث آزار توسعه دهندگان می شد. چند سال پیش تیم توسعه Node.js شروع به پیاده سازی ES Modules کرد اما در ابتدا این قابلیت پشتیبانی کامل نداشت و با فلگ experimental-module-- قابل استفاده بود. در نهایت در نوامبر ۲۰۱۹ تیم توسعه Node.js پستی را در medium منتشر کرد و از پشتیبانی کامل ES Modules خبر داد.

در حال حاضر برای استفاده از ES Modules در Node.js باید از یکی از روش های زیر استفاده کنید:

  • به جای js برای پسوند فایل ها از پسوند mjs استفاده کنید.
  • به فایل package.json رفته و فیلدی به نام type را در آن تعریف کنید که مقدارش برابر با module باشد، یعنی به شکل "type": "module" باشد.
  • استفاده از فلگ input-type=module-- برای رشته های پاس داده شده به eval--

معمولا ساده ترین راه، روش دوم (ویرایش package.json) است. در صورتی که این کار را نکنید تمام فایل ها بر اساس CommonJS پردازش می شوند بنابراین اگر در کدهایتان از ES Modules استفاده کرده باشید با خطای زیر روبرو می شوید:

(node:2844) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.(Use `node --trace-warnings ...` to show where the warning was created). Also, as an aside, we cannot make use of import statements outside of modules.

از آنجایی که ممکن است رفتار پیش فرض Node.js در آینده تغییر کند، تیم توسعه Node پیشنهاد می کند همیشه با فیلد type نوع ماژول هایتان را مشخص کنید؛ حتی اگر می خواهید از CommonJs استفاده کنید مقدار type در package.json را برابر commonjs بگذارید تا به صورت صریح مشخص شود.

در حال حاضر اگر به documentation رسمی node.js برای ES Modules (در زمان نگارش این مقاله، نسخه ۱۶) بروید می بینید که پشتیبانی از ES Modules در حالت stable (پایدار و کامل) می باشد. این مسئله برای نسخه های ۱۵ و ۱۲ نیز صحیح است (البته به شرطی که نسخه Node خود را به روز رسانی کنید). در نسخه های دیگر مانند ۱۳ و ۱۴ پشتیبانی به صورت Experimental یا آزمایشی است، بدین معنی که کار می کند اما ممکن است در آینده با آپدیت های بعدی به طور کامل حذف شود و یا تغییراتی را داشته باشد که نسخه های قبلی را از بین ببرد بنابراین استفاده از آن ها پیشنهاد نمی شود. قانون کلی دنیای نرم افزار را یادتان نرود، همیشه از آخرین نسخه پایدار استفاده کنید که در هنگام نگارش این مقاله نسخه ۱۶ است.

اگر از کاربران تایپ اسکریپت هستید نیز هیچ مشکلی نخواهید داشت چرا که تایپ اسکریپت از آخرین ویژگی های موجود استفاده کرده و سپس در صورت نیاز کدها را به نسخه های دیگر کامپایل می کند.


منبع: وب سایت javascript

نویسنده شوید
دیدگاه‌های شما (2 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

محیا
07 بهمن 1401
مقاله عالی بود خیلی ممنون ..کاملا متوجه شدم ..فقط من لایو سرو برای وی اس کد دانلود کردم ازش استفاده می کنم ولی هنوز درست کار نمی ده برای استفاد از نود جی اس این فایل packge.json باید خودمون بسازیمش ؟

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

Entegad
26 خرداد 1400
چرا شماها نمیتونید این مقاله رو تو چند پست قرار بدین؟؟؟؟ یعنی انقد سخته که یه مقاله رو چند قسمت کنید ؟؟؟

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

روکسو
27 خرداد 1400
قبلا مقالات رو چند قسمت می‌کردیم، دوستان اعتراض می‌کردند که چرا چند قسمته. برای شما چه فرقی داره دوست عزیز؟ می‌تونید یک مقاله رو در زمان‌های مختلف مطالعه کنید. مثل یک کتاب که در دسترستون هست و هر موقع بخواید می‌تونید یک قسمتش رو مطالعه کنید.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

محسن
24 شهریور 1401
دوست عزیز ادب و احترام رو رعایت کن. شما برای خودت اسکرین شات های مجزا بگیر استفاده کن. نحوه انتشار مطالب به این شیوه بهتره.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.