لاکھوں فرنٹ اینڈ ایپلی کیشنز ماحول سے متعلق مخصوص تعمیرات کا انتظام کر رہی ہیں۔ ہر ماحول کے لیے — چاہے وہ ترقی ہو، سٹیجنگ ہو، یا پروڈکشن — فرنٹ اینڈ ایپ کی ایک علیحدہ تعمیر لازمی ہے، اور صحیح ماحول کے متغیرات کو ترتیب دیا جانا چاہیے۔ اگر متعدد ایپس شامل ہوں تو تعمیرات کی تعداد بڑھ جاتی ہے، جس سے مایوسی میں اضافہ ہوتا ہے۔ یہ ایک طویل عرصے سے ایک عام مسئلہ رہا ہے، لیکن ماحولیاتی متغیرات کو سنبھالنے کا ایک بہتر طریقہ ہے۔ میں نے اس عمل کو ہموار کرنے کا ایک طریقہ ڈھونڈ لیا ہے، اور اس مضمون میں، میں آپ کو ایک موثر عمل بنانے کے لیے قدم بہ قدم رہنمائی کروں گا جو تعمیر کے اوقات کو کم کرے گا اور آپ کو آپ کے پروجیکٹس میں ماحول میں مستقل مزاجی کو یقینی بنانے میں مدد ملے گی۔
اس سے پہلے کہ ہم شروع کریں، میرے خیال میں ہمیں ایک ریکاپ کرنا چاہیے۔ ویب ایپلیکیشنز تقریباً ہمیشہ ان متغیرات پر انحصار کرتی ہیں جنہیں "ماحولیاتی متغیرات " کہا جاتا ہے، جس میں اکثر داخلی نظام کے اختتامی نقطہ، انضمام کے نظام، ادائیگی کے نظام کی چابیاں، ریلیز نمبرز وغیرہ شامل ہوتے ہیں۔ قدرتی طور پر، ان متغیرات کی قدریں اس ماحول کے لحاظ سے مختلف ہوتی ہیں جس میں ایپلی کیشن کو تعینات کیا گیا ہے۔
مثال کے طور پر، ایک ایسی ایپلیکیشن کا تصور کریں جو ادائیگی کے گیٹ وے کے ساتھ تعامل کرتی ہے۔ ترقیاتی ماحول میں، ادائیگی کے گیٹ وے کا URL جانچ کے لیے ایک سینڈ باکس کی طرف اشارہ کر سکتا ہے (https://sandbox.paymentgateway.com)، جبکہ پیداواری ماحول میں، یہ لائیو سروس (https://live.paymentgateway.com) کی طرف اشارہ کرتا ہے۔ )۔ اسی طرح، ڈیٹا کی حفاظت کو یقینی بنانے اور ماحول کے اختلاط سے بچنے کے لیے ہر ماحول کے لیے مختلف API کیز یا کوئی دوسری ماحول کی مخصوص ترتیب استعمال کی جاتی ہے۔
بیک اینڈ ایپلی کیشنز بناتے وقت، یہ کوئی مسئلہ نہیں ہے۔ ایپلیکیشن کوڈ میں ان متغیرات کا اعلان کرنا کافی ہے کیونکہ ان متغیرات کی قدریں سرور کے ماحول میں محفوظ کی جاتی ہیں جہاں بیک اینڈ کو تعینات کیا جاتا ہے۔ اس طرح، بیک اینڈ ایپلی کیشن اسٹارٹ اپ پر ان تک رسائی حاصل کرتی ہے۔
تاہم، فرنٹ اینڈ ایپلی کیشنز کے ساتھ چیزیں کچھ زیادہ پیچیدہ ہوجاتی ہیں۔ چونکہ وہ صارف کے براؤزر میں چلائے جاتے ہیں، اس لیے انہیں مخصوص ماحولیاتی متغیر اقدار تک رسائی حاصل نہیں ہوتی۔ اس سے نمٹنے کے لیے، ان متغیرات کی قدروں کو عام طور پر تعمیر کے وقت فرنٹ اینڈ ایپلی کیشن میں "بیک ان" کیا جاتا ہے۔ اس طرح، جب ایپلیکیشن صارف کے براؤزر میں چلتی ہے، تمام ضروری اقدار پہلے سے ہی فرنٹ اینڈ ایپلی کیشن میں سرایت کر جاتی ہیں۔
یہ نقطہ نظر، بہت سے دوسرے لوگوں کی طرح، ایک انتباہ کے ساتھ آتا ہے: آپ کو ہر ماحول کے لیے ایک ہی فرنٹ اینڈ ایپلی کیشن کی ایک الگ بلڈ بنانے کی ضرورت ہے تاکہ ہر بلڈ میں اس کی متعلقہ اقدار ہوں۔
مثال کے طور پر ، ہم کہتے ہیں کہ ہمارے پاس تین ماحول ہیں:
اندرونی جانچ کے لئے ترقی؛
انضمام کی جانچ کا مرحلہ؛
اور گاہکوں کے لئے پیداوار.
اپنے کام کو جانچ کے لیے جمع کرانے کے لیے، آپ ایپ بناتے ہیں اور اسے ترقیاتی ماحول میں تعینات کرتے ہیں۔ داخلی جانچ مکمل ہونے کے بعد، آپ کو ایپ کو اسٹیج پر تعینات کرنے کے لیے دوبارہ بنانا ہوگا، اور پھر اسے پروڈکشن میں تعینات کرنے کے لیے دوبارہ بنانا ہوگا۔ اگر پروجیکٹ میں ایک سے زیادہ فرنٹ اینڈ ایپلی کیشنز شامل ہیں، تو اس طرح کی تعمیرات کی تعداد نمایاں طور پر بڑھ جاتی ہے۔ اس کے علاوہ، ان تعمیرات کے درمیان، کوڈبیس تبدیل نہیں ہو رہا ہے — دوسری اور تیسری تعمیرات ایک ہی سورس کوڈ پر مبنی ہیں۔
یہ سب کچھ رہائی کے عمل کو بھاری، سست اور مہنگا بناتا ہے، اور ساتھ ہی ساتھ معیار کی یقین دہانی کا خطرہ بھی لاحق ہوتا ہے۔ ہوسکتا ہے کہ ترقی کے ماحول میں تعمیر کا اچھی طرح سے تجربہ کیا گیا ہو، لیکن اسٹیج کی تعمیر تکنیکی طور پر نئی ہے، یعنی اب غلطی کے نئے امکانات موجود ہیں۔
ایک مثال: آپ کے پاس دو ایپلیکیشنز ہیں جن میں X اور Y سیکنڈز کے بلڈ ٹائم ہیں۔ ان تینوں ماحول کے لیے، دونوں ایپس کو تعمیراتی وقت میں 3X + 3Y لگے گا۔ تاہم، اگر آپ ہر ایپ کو صرف ایک بار بنا سکتے ہیں اور تمام ماحول میں اسی تعمیر کو استعمال کر سکتے ہیں، تو کل وقت صرف X + Y سیکنڈ رہ جائے گا، جس سے تعمیر کے وقت کو تین گنا کم کیا جائے گا۔
اس سے فرنٹ اینڈ پائپ لائنوں میں بڑا فرق پڑ سکتا ہے، جہاں وسائل محدود ہیں اور تعمیر کا وقت صرف چند منٹوں سے لے کر ایک گھنٹے تک ہو سکتا ہے۔ یہ مسئلہ عالمی سطح پر تقریباً ہر فرنٹ اینڈ ایپلی کیشن میں موجود ہے، اور اکثر اسے حل کرنے کا کوئی طریقہ نہیں ہوتا ہے۔ پھر بھی، یہ ایک سنگین مسئلہ ہے، خاص طور پر کاروباری نقطہ نظر سے۔
کیا یہ اچھا نہیں ہوگا اگر تین الگ الگ بلڈ بنانے کے بجائے، آپ صرف ایک بنا کر اسے تمام ماحول میں تعینات کر سکتے ہیں؟ ٹھیک ہے، مجھے بالکل ایسا کرنے کا ایک طریقہ مل گیا ہے۔
ماحولیاتی متغیرات کو ترتیب دینا
سب سے پہلے، آپ کو اپنے فرنٹ اینڈ پروجیکٹ کے ذخیرے میں ایک فائل بنانے کی ضرورت ہے جہاں مطلوبہ ماحولیاتی متغیرات درج ہوں گے۔ یہ ڈویلپر مقامی طور پر استعمال کریں گے۔ عام طور پر، اس فائل کو کہا جاتا ہے .env.local
، جسے زیادہ تر جدید فرنٹ اینڈ فریم ورک پڑھ سکتے ہیں۔ یہاں ایسی فائل کی ایک مثال ہے:
CLIENT_ID='frontend-development' API_URL=/api/v1' PUBLIC_URL='/' COMMIT_SHA=''
نوٹ: مختلف فریم ورک کو ماحولیاتی متغیرات کے لیے مختلف نام دینے کے کنونشنز کی ضرورت ہوتی ہے۔ مثال کے طور پر، React میں، آپ کو REACT_APP_
متغیر ناموں سے پہلے جوڑنے کی ضرورت ہے۔ ضروری نہیں کہ اس فائل میں ایسے متغیرات کو شامل کیا جائے جو درخواست کو براہ راست متاثر کرتے ہیں۔ اس میں ڈیبگنگ کی مددگار معلومات بھی شامل ہو سکتی ہیں۔ میں نے COMMIT_SHA
متغیر شامل کیا، جسے ہم بعد میں تعمیراتی کام سے کھینچیں گے تاکہ اس کمٹ کو ٹریک کیا جا سکے جس پر یہ تعمیر تھی۔
اگلا، ایک فائل بنائیں environment.js
، جہاں آپ وضاحت کر سکتے ہیں کہ آپ کو کون سے ماحولیاتی متغیرات کی ضرورت ہے۔ فرنٹ اینڈ فریم ورک ان کو آپ کے لیے انجیکشن دے گا۔ React کے لیے، مثال کے طور پر، وہ process.env
آبجیکٹ میں محفوظ ہیں:
const ORIGIN_ENVIRONMENTS = window.ORIGIN_ENVIRONMENTS = { CLIENT_ID: process.env.CLIENT_ID, API_URL: process.env.API_URL, PUBLIC_URL: process.env.PUBLIC_URL, COMMIT_SHA: process.env.COMMIT_SHA }; export const ENVIRONMENT = { clientId: ORIGIN_ENVIRONMENTS.CLIENT_ID, apiUrl: ORIGIN_ENVIRONMENTS.API_URL, publicUrl: ORIGIN_ENVIRONMENTS.PUBLIC_URL ?? "/", commitSha: ORIGIN_ENVIRONMENTS.COMMIT_SHA, };
یہاں، آپ window.ORIGIN_ENVIRONMENTS
آبجیکٹ میں متغیرات کے لیے تمام ابتدائی قدروں کو بازیافت کرتے ہیں، جو آپ کو انہیں براؤزر کے کنسول میں دیکھنے کی اجازت دیتا ہے۔ اس کے علاوہ، آپ کو انہیں ENVIRONMENT
آبجیکٹ میں کاپی کرنے کی ضرورت ہے، جہاں آپ کچھ ڈیفالٹس بھی سیٹ کر سکتے ہیں، مثال کے طور پر: ہم فرض کرتے ہیں کہ publicUrl
/بذریعہ ڈیفالٹ ہے۔ جہاں بھی درخواست میں ان متغیرات کی ضرورت ہو وہاں ENVIRONMENT
آبجیکٹ کا استعمال کریں۔
اس مرحلے پر، آپ نے مقامی ترقی کی تمام ضروریات پوری کر لی ہیں۔ لیکن مقصد مختلف ماحول کو سنبھالنا ہے۔
ایسا کرنے کے لیے درج ذیل مواد کے ساتھ ایک .env
فائل بنائیں۔
CLIENT_ID='<client_id>' API_URL='<api_url>' PUBLIC_URL='<public_url>' COMMIT_SHA=$COMMIT_SHA
اس فائل میں، آپ ان متغیرات کے لیے پلیس ہولڈرز کی وضاحت کرنا چاہیں گے جو ماحول پر منحصر ہیں۔ وہ کچھ بھی ہو سکتے ہیں جو آپ چاہتے ہیں، جب تک کہ وہ منفرد ہوں اور آپ کے سورس کوڈ کے ساتھ کسی بھی طرح اوورلیپ نہ ہوں۔ اضافی یقین دہانی کے لیے، آپ استعمال بھی کر سکتے ہیں۔
ان متغیرات کے لیے جو پورے ماحول میں تبدیل نہیں ہوتے ہیں (مثلاً کمٹ ہیش)، آپ یا تو اصل قدریں براہ راست لکھ سکتے ہیں یا وہ اقدار استعمال کر سکتے ہیں جو تعمیراتی کام کے دوران دستیاب ہوں گی (جیسے $COMMIT_SHA )۔ فرنٹ اینڈ فریم ورک تعمیر کے عمل کے دوران ان پلیس ہولڈرز کو حقیقی اقدار سے بدل دے گا:
فائل
اب آپ کو پلیس ہولڈرز کے بجائے حقیقی قدریں ڈالنے کا موقع ملتا ہے۔ ایسا کرنے کے لیے، ایک فائل بنائیں، inject.py
(میں نے ازگر کا انتخاب کیا، لیکن آپ اس مقصد کے لیے کوئی بھی ٹول استعمال کر سکتے ہیں)، جس میں پہلے پلیس ہولڈرز کی متغیر ناموں کی میپنگ ہونی چاہیے:
replacement_map = { "<client_id>": "CLIENT_ID", "<api_url>": "API_URL", "<public_url>": "PUBLIC_URL", "%3Cpublic_url%3E": "PUBLIC_URL" }
نوٹ کریں کہ public_url
دو بار درج ہے، اور دوسرا اندراج بریکٹ سے بچ گیا ہے۔ آپ کو ان تمام متغیرات کے لیے اس کی ضرورت ہے جو CSS اور HTML فائلوں میں استعمال ہوتے ہیں۔
base_path = 'usr/share/nginx/html' target_files = [ f'{base_path}/static/js/main.*.js', f'{base_path}/static/js/chunk.*.js', f'{base_path}/static/css/main.*.css', f'{base_path}/static/css/chunk.*.css', f'{base_path}/index.html' ]
injector.py
فائل بناتے ہیں، جہاں ہمیں نقشہ سازی اور بلڈ آرٹفیکٹ فائلوں کی فہرست موصول ہوگی (جیسے JS، HTML، اور CSS فائلیں) اور پلیس ہولڈرز کو ہمارے موجودہ ماحول سے متغیرات کی قدروں سے بدل دیں گے:
import os import glob def inject_envs(filename, replacement_map): with open(filename) as r: lines = r.read() for key, value in replacement_map.items(): lines = lines.replace(key, os.environ.get(value) or '') with open(filename, "w") as w: w.write(lines) def inject(target_files, replacement_map, base_path): for target_file in target_files: for filename in glob.glob(target_file.glob): inject_envs(filename, replacement_map)
اور پھر، inject.py
فائل میں، یہ لائن شامل کریں ( injector.py
درآمد کرنا نہ بھولیں):
injector.inject(target_files, replacement_map, base_path)
inject.py
اسکرپٹ صرف تعیناتی کے دوران چلتی ہے۔ آپ اسے Python انسٹال کرنے اور تمام نمونے کاپی کرنے کے بعد CMD
کمانڈ میں Dockerfile
میں شامل کر سکتے ہیں: RUN apk add python3 COPY nginx/default.conf /etc/nginx/conf.d/default.conf COPY --from=build /app/ci /ci COPY --from=build /app/build /usr/share/nginx/html CMD ["/bin/sh", "-c", "python3 ./ci/inject.py && nginx -g 'daemon off;'"]That's it! This way, during each deployment, the pre-built files will be used, with variables specific to the deployment environment injected into them.
بس! اس طرح، ہر تعیناتی کے دوران، پہلے سے بنی فائلوں کو استعمال کیا جائے گا، ان میں تعیناتی ماحول کے لیے مخصوص متغیرات کے ساتھ۔
فائل:
ایک چیز - اگر آپ کے تعمیراتی نمونوں میں ان کے فائل ناموں میں مواد کی ہیش شامل ہے، تو یہ انجیکشن فائل ناموں کو متاثر نہیں کرے گا اور اس سے براؤزر کیشنگ میں مسائل پیدا ہو سکتے ہیں۔ اسے ٹھیک کرنے کے لیے، انجکشن شدہ متغیرات کے ساتھ فائلوں میں ترمیم کرنے کے بعد، آپ کو یہ کرنے کی ضرورت ہوگی:
اس کو نافذ کرنے کے لیے، ایک ہیش لائبریری امپورٹ ( import hashlib
) اور درج ذیل فنکشنز inject.py
فائل میں شامل کریں۔
def sha256sum(filename): h = hashlib.sha256() b = bytearray(128 * 1024) mv = memoryview(b) with open(filename, 'rb', buffering=0) as f: while n := f.readinto(mv): h.update(mv[:n]) return h.hexdigest() def replace_filename_imports(filename, new_filename, base_path): allowed_extensions = ('.html', '.js', '.css') for path, dirc, files in os.walk(base_path): for name in files: current_filename = os.path.join(path, name) if current_filename.endswith(allowed_extensions): with open(current_filename) as f: s = f.read() s = s.replace(filename, new_filename) with open(current_filename, "w") as f: f.write(s) def rename_file(fullfilename): dirname = os.path.dirname(fullfilename) filename, ext = os.path.splitext(os.path.basename(fullfilename)) digest = sha256sum(fullfilename) new_filename = f'{filename}.{digest[:8]}' new_fullfilename = f'{dirname}/{new_filename}{ext}' os.rename(fullfilename, new_fullfilename) return filename, new_filename
پھر بھی، تمام فائلوں کا نام تبدیل کرنے کی ضرورت نہیں ہے۔ مثال کے طور پر، index.html
فائل کا نام تبدیل نہیں ہونا چاہیے اور اس کو حاصل کرنے کے لیے، ایک TargetFile
کلاس بنائیں جو ایک جھنڈا ذخیرہ کرے گا جس سے یہ ظاہر ہوتا ہے کہ آیا نام تبدیل کرنا ضروری ہے:
class TargetFile: def __init__(self, glob, should_be_renamed = True): self.glob = glob self.should_be_renamed = should_be_renamed
اب آپ کو صرف inject.py
میں فائل پاتھ کی صف کو TargetFile
کلاس آبجیکٹ کی ایک صف سے تبدیل کرنا ہے۔
target_files = [ injector.TargetFile(f'{base_path}/static/js/main.*.js'), injector.TargetFile(f'{base_path}/static/js/chunk.*.js'), injector.TargetFile(f'{base_path}/static/css/main.*.css'), injector.TargetFile(f'{base_path}/static/css/chunk.*.css'), injector.TargetFile(f'{base_path}/index.html', False) ]
اور injector.py
میں inject
فنکشن کو اپ ڈیٹ کریں تاکہ اگر جھنڈا سیٹ ہو تو فائل کا نام تبدیل کرنا شامل ہو:
def inject(target_files, replacement_map, base_path): for target_file in target_files: for filename in glob.glob(target_file.glob): inject_envs(filename, replacement_map) if target_file.should_be_renamed: filename, new_filename = rename_file(filename) replace_filename_imports(filename, new_filename, base_path)
نتیجے کے طور پر، آرٹفیکٹ فائلیں اس نام کے فارمیٹ کی پیروی کریں گی: <origin-file-name>
۔ <injection-hash>
. <extension>
>
انجیکشن سے پہلے فائل کا نام:
انجیکشن کے بعد فائل کا نام:
ایک جیسے ماحول کے متغیرات سے ایک ہی فائل کا نام نکلتا ہے، جس سے صارف کے براؤزر کو فائل کو صحیح طریقے سے کیش کرنے کی اجازت ملتی ہے۔ اب اس بات کی ضمانت ہے کہ ان متغیرات کی درست قدریں براؤزر کیشے میں محفوظ ہو جائیں گی، نتیجے کے طور پر - کلائنٹ کے لیے بہتر کارکردگی۔
ہر ماحول کے لیے الگ الگ تعمیرات بنانے کے روایتی انداز نے چند اہم ناکاریاں پیدا کی ہیں، جو محدود وسائل والی ٹیموں کے لیے ایک مسئلہ بن سکتی ہیں۔
اب آپ کے پاس ریلیز کے عمل کے لیے ایک بلیو پرنٹ ہے جو طویل تعیناتی کے اوقات، ضرورت سے زیادہ تعمیرات، اور فرنٹ اینڈ ایپلی کیشنز کے لیے کوالٹی ایشورنس میں بڑھتے ہوئے خطرات کو حل کر سکتا ہے - یہ سب کچھ۔ تمام ماحول میں گارنٹی شدہ مستقل مزاجی کی ایک نئی سطح متعارف کرواتے ہوئے
N تعمیرات کی ضرورت کے بجائے، آپ کو صرف ایک کی ضرورت ہوگی۔ آنے والی ریلیز کے لیے، آپ آسانی سے اس بلڈ کو تعینات کر سکتے ہیں جس کا پہلے سے تجربہ کیا گیا تھا، جو ممکنہ بگ کے مسائل کو حل کرنے میں بھی مدد کرتا ہے کیونکہ ایک ہی بلڈ کو تمام ماحول میں استعمال کیا جائے گا۔ اس کے علاوہ، اس اسکرپٹ کی عمل آوری کی رفتار بھی سب سے زیادہ بہتر بنائی گئی تعمیر سے کہیں زیادہ تیز ہے۔ مثال کے طور پر، MacBook 14 PRO, M1, 32GB پر مقامی بینچ مارکس درج ذیل ہیں:
میرا نقطہ نظر ریلیز کے عمل کو آسان بناتا ہے، کیشنگ کی موثر حکمت عملیوں کی اجازت دے کر ایپلی کیشن کی کارکردگی کو برقرار رکھتا ہے، اور اس بات کو یقینی بناتا ہے کہ تعمیر سے متعلق کیڑے اسے ماحول میں نہیں بنائیں گے۔ اس کے علاوہ، تمام وقت اور محنت جو پہلے تھکا دینے والے تعمیراتی کاموں پر خرچ کی گئی تھی اب اس سے بھی بہتر صارف کا تجربہ بنانے پر توجہ مرکوز کی جا سکتی ہے۔ کیا محبت نہیں ہے؟
ہم اس بات کو یقینی بناتے ہیں کہ تعمیر سے متعلق کیڑے دوسرے ماحول کے لیے ایپ میں نہ پھسل جائیں۔ پریت کیڑے ہو سکتے ہیں جو تعمیراتی نظام میں خامیوں کی وجہ سے ظاہر ہوتے ہیں۔ امکانات پتلے ہیں، لیکن وہ موجود ہیں.