برنامه های وب خود را با ابزار مدرن داربست کنید
مقدمه
Allo' Allo'. هر کسی که یک برنامه وب می نویسد می داند که چقدر مهم است که خود را سازنده نگه دارد. زمانی که باید نگران کارهای طاقت فرسایی مانند پیدا کردن دیگ بخار مناسب، تنظیم گردش کار توسعه و آزمایش و کوچک سازی و فشرده سازی همه منابع خود باشید، این یک چالش است.
خوشبختانه ابزارهای جلویی مدرن میتوانند به خودکارسازی بسیاری از این موارد کمک کنند و شما را بر روی نوشتن یک برنامه کاربردی تمرکز کنید. این مقاله به شما نشان می دهد که چگونه از Yeoman استفاده کنید، یک گردش کار از ابزارها برای برنامه های وب برای ساده سازی ایجاد برنامه ها با استفاده از Polymer ، یک کتابخانه از polyfills و شکر برای توسعه برنامه ها با استفاده از Web Components .
یو، گرانت و بوور را ملاقات کنید
یئومن مردی است با کلاه با سه ابزار برای بهبود بهره وری:
- yo یک ابزار داربست است که اکوسیستمی از داربست های خاص چارچوب را ارائه می دهد، به نام ژنراتور که می تواند برای انجام برخی از کارهای خسته کننده ای که قبلا ذکر کردم استفاده شود.
- grunt برای ساخت، پیش نمایش و آزمایش پروژه شما استفاده می شود، به لطف کمک از وظایفی که توسط تیم Yeoman و grunt-contrib تنظیم شده است.
- bower برای مدیریت وابستگی استفاده می شود، به طوری که دیگر نیازی به دانلود و مدیریت دستی اسکریپت های خود ندارید.
تنها با یک یا دو دستور، Yeoman میتواند کد boilerplate را برای برنامه شما (یا تکههای جداگانه مانند Models) بنویسد، Sass شما را کامپایل کند، CSS، JS، HTML و تصاویر شما را کوچکسازی و به هم متصل کند و یک وب سرور ساده را در فهرست فعلی شما راهاندازی کند. همچنین می تواند تست های واحد شما و موارد دیگر را اجرا کند.
شما می توانید ژنراتورها را از Node Packaged Modules (npm) نصب کنید و اکنون بیش از 220 ژنراتور در دسترس هستند که بسیاری از آنها توسط جامعه منبع باز نوشته شده اند. ژنراتورهای محبوب عبارتند از generator-angular ، generator-backbone و generator-ember .
با نصب آخرین نسخه Node.js ، به نزدیکترین ترمینال خود بروید و اجرا کنید:
$ npm install -g yo
همین! اکنون Yo، Grunt و Bower را دارید و می توانید آنها را مستقیماً از خط فرمان اجرا کنید. در اینجا خروجی اجرای yo
آمده است:
ژنراتور پلیمری
همانطور که قبلاً اشاره کردم، Polymer یک کتابخانه از polyfills و شکر است که امکان استفاده از Web Components را در مرورگرهای مدرن فراهم می کند. این پروژه به توسعه دهندگان این امکان را می دهد تا با استفاده از پلتفرم فردا اپلیکیشن بسازند و W3C را از مکان هایی که می توانند مشخصات پروازی را بیشتر بهبود بخشند، اطلاع دهند.
generator-polymer یک ژنراتور جدید است که به شما کمک میکند تا برنامههای پلیمر را با استفاده از Yeoman داربستبندی کنید و به شما امکان میدهد به راحتی عناصر پلیمر (سفارشی) را از طریق خط فرمان ایجاد و سفارشی کنید و با استفاده از HTML Imports وارد کنید. این کار با نوشتن کد دیگ بخار برای شما در وقت شما صرفه جویی می کند.
در مرحله بعد، ژنراتور پلیمر را با اجرای زیر نصب کنید:
$ npm install generator-polymer -g
همین. اکنون برنامه شما دارای قدرت های فوق العاده Web Component است!
ژنراتور تازه نصب شده ما دارای چند بیت خاص است که به آنها دسترسی خواهید داشت:
-
polymer:element
برای داربست کردن عناصر جدید پلیمری استفاده می شود. برای مثال:yo polymer:element carousel
-
polymer:app
برای ایجاد داربست index.html اولیه شما استفاده می شود، یک Gruntfile.js حاوی پیکربندی زمان ساخت برای پروژه شما و همچنین وظایف Grunt و ساختار پوشه توصیه شده برای پروژه. همچنین به شما این امکان را می دهد که از Sass Bootstrap برای سبک های پروژه خود استفاده کنید.
بیایید یک برنامه پلیمر بسازیم
ما قصد داریم با استفاده از برخی عناصر سفارشی پلیمر و ژنراتور جدیدمان یک وبلاگ ساده بسازیم.
برای شروع، به ترمینال بروید، یک دایرکتوری جدید بسازید و با استفاده از mkdir my-new-project && cd $_
در آن سی دی ایجاد کنید. اکنون میتوانید برنامه Polymer خود را با اجرای زیر شروع کنید:
$ yo polymer
این آخرین نسخه Polymer را از Bower دریافت میکند و یک index.html، ساختار دایرکتوری و وظایف Grunt را برای گردش کار شما ایجاد میکند. چرا وقتی منتظریم تا برنامه آماده شود، قهوه نخورید؟
بسیار خوب، در ادامه میتوانیم grunt server
برای پیشنمایش ظاهر برنامه اجرا کنیم:
سرور از LiveReload پشتیبانی می کند، به این معنی که می توانید یک ویرایشگر متن را راه اندازی کنید، یک عنصر سفارشی را ویرایش کنید و مرورگر در ذخیره مجدد بارگیری می شود. این به شما یک نمای زمان واقعی از وضعیت فعلی برنامه شما می دهد.
در مرحله بعد، بیایید یک عنصر پلیمر جدید برای نمایش یک پست وبلاگ ایجاد کنیم.
$ yo polymer:element post
Yeoman از ما چند سوال می پرسد، مانند اینکه آیا مایلیم یک سازنده اضافه کنیم یا از یک HTML Import برای گنجاندن عنصر پست در index.html
استفاده کنیم. فعلاً به دو گزینه اول نه بگوییم و گزینه سوم را خالی بگذاریم.
$ yo polymer:element post
[?] Would you like to include constructor=''? No
[?] Import to your index.html using HTML imports? No
[?] Import other elements into this one? (e.g 'another_element.html' or leave blank)
create app/elements/post.html
این یک عنصر پلیمر جدید در پوشه /elements
به نام post.html ایجاد می کند:
<polymer-element name="post-element" attributes="">
<template>
<style>
@host { :scope {display: block;} }
</style>
<span>I'm <b>post-element</b>. This is my Shadow DOM.</span>
</template>
<script>
Polymer('post-element', {
//applyAuthorStyles: true,
//resetStyleInheritance: true,
created: function() { },
enteredView: function() { },
leftView: function() { },
attributeChanged: function(attrName, oldVal, newVal) { }
});
</script>
</polymer-element>
شامل:
- کد Boilerplate برای عنصر سفارشی خود، به شما این امکان را می دهد که از یک نوع عنصر سفارشی DOM در صفحه خود استفاده کنید (مثلا
<post-element>
) - یک تگ قالب برای الگوی «بومی» در سمت مشتری و نمونه سبکهای محدودهای برای کپسوله کردن سبکهای عنصر شما
- دیگ ثبت عنصر و رویدادهای چرخه حیات .
کار با منبع واقعی داده
وبلاگ ما به مکانی برای نوشتن و خواندن پست های جدید نیاز دارد. برای نشان دادن کار با یک سرویس داده واقعی، از Google Apps Spreadsheets API استفاده می کنیم. این به ما امکان می دهد به راحتی محتوای صفحه گسترده ایجاد شده با استفاده از Google Docs را بخوانیم.
بیایید این تنظیم را انجام دهیم:
در مرورگر خود (برای این مراحل، Chrome توصیه می شود) این صفحه گسترده Google Docs را باز کنید. این شامل داده های پست نمونه در فیلدهای زیر است:
- شناسه
- عنوان
- نویسنده
- محتوا
- تاریخ
- کلمات کلیدی
- ایمیل (نویسنده)
- Slug (برای URL Slug پست شما)
به منوی File رفته و گزینه Make a copy را انتخاب کنید تا کپی خود را از صفحه گسترده ایجاد کنید. شما آزاد هستید که محتوا را در اوقات فراغت خود ویرایش کنید، پست ها را اضافه یا حذف کنید.
یک بار دیگر به منوی File رفته و گزینه Publish to the web را انتخاب کنید.
روی شروع انتشار کلیک کنید
در قسمت دریافت پیوند به داده های منتشر شده ، از آخرین کادر متنی، بخش کلیدی URL ارائه شده را کپی کنید. به نظر می رسد: https://docs.google.com/spreadsheet/ccc?key=0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc#gid=0
کلید را در URL زیر جایگذاری کنید، جایی که عبارت your-key-goes-here : https://spreadsheets.google.com/feeds/list/your-key-goes-here/od6/public/values?alt=json- in-script&callback= . نمونه ای با استفاده از کلید بالا ممکن است شبیه https://spreadsheets.google.com/feeds/list/0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc/od6/public/values?alt=json-in-script باشد.
میتوانید URL را در مرورگر خود جایگذاری کنید و برای مشاهده نسخه JSON محتوای وبلاگ خود به آن بروید. URL را یادداشت کنید سپس کمی زمان را صرف بررسی قالب این داده ها کنید زیرا برای نمایش دادن آن در صفحه بعداً باید روی آن تکرار کنید.
خروجی JSON در مرورگر شما ممکن است کمی دلهره آور به نظر برسد، اما نگران نباشید! ما واقعاً فقط به داده های پست های شما علاقه مندیم.
Google Spreadsheets API هر یک از فیلدهای صفحه گسترده وبلاگ شما را با پیشوند ویژه post.gsx$
خروجی می دهد. به عنوان مثال: post.gsx$title.$t
، post.gsx$author.$t
، post.gsx$content.$t
و غیره. وقتی روی هر «ردیف» در خروجی JSON خود تکرار میکنیم، به این فیلدها ارجاع میدهیم تا مقادیر مربوط به هر پست را برگردانیم.
اکنون می توانید عنصر پست جدید داربست خود را ویرایش کنید تا بخش هایی از نشانه گذاری را به داده های صفحه گسترده خود متصل کنید . برای انجام این کار، یک post
مشخصه را معرفی می کنیم که برای عنوان پست، نویسنده، محتوا و سایر فیلدهایی که قبلا ایجاد کردیم خوانده می شود. ویژگی selected
(که بعداً آن را پر خواهیم کرد) فقط برای نمایش یک پست در صورتی استفاده می شود که کاربر به سمت اسلاگ صحیح برای آن حرکت کند.
<polymer-element name="post-element" attributes="post selected">
<template>
<style>
@host { :scope {display: block;} }
</style>
<div class="col-lg-4">
<template if="[[post.gsx$slug.$t === selected]]">
<h2>
<a href="#[[post.gsx$slug.$t]]">
[[post.gsx$title.$t ]]
</a>
</h2>
<p>By [[post.gsx$author.$t]]</p>
<p>[[post.gsx$content.$t]]</p>
<p>Published on: [[post.gsx$date.$t]]</p>
<small>Keywords: [[post.gsx$keywords.$t]]</small>
</template>
</div>
</template>
<script>
Polymer('post-element', {
created: function() { },
enteredView: function() { },
leftView: function() { },
attributeChanged: function(attrName, oldVal, newVal) { }
});
</script>
</polymer-element>
در مرحله بعد، بیایید با اجرای yo polymer:element blog
یک عنصر وبلاگ ایجاد کنیم که شامل مجموعه ای از پست ها و طرح بندی وبلاگ شما باشد.
$ yo polymer:element blog
[?] Would you like to include constructor=''? No
[?] Import to your index.html using HTML imports? Yes
[?] Import other elements into this one? (e.g 'another_element.html' or leave blank) post.html
create app/elements/blog.html
این بار ما وبلاگ را با استفاده از واردات HTML به شکلی که میخواهیم در صفحه نمایش داده شود به index.html وارد میکنیم. مخصوصاً برای فرمان سوم، post.html
به عنوان عنصری که میخواهیم در آن قرار دهیم، مشخص میکنیم.
مانند قبل، یک فایل عنصر جدید ایجاد می شود (blog.html) و به عناصر / اضافه می شود، این بار post.html را وارد می کند و <post-element>
را در تگ الگو قرار می دهد:
<link rel="import" href="post.html">
<polymer-element name="blog-element" attributes="">
<template>
<style>
@host { :scope {display: block;} }
</style>
<span>I'm <b>blog-element</b>. This is my Shadow DOM.</span>
<post-element></post-element>
</template>
<script>
Polymer('blog-element', {
//applyAuthorStyles: true,
//resetStyleInheritance: true,
created: function() { },
enteredView: function() { },
leftView: function() { },
attributeChanged: function(attrName, oldVal, newVal) { }
});
</script>
</polymer-element>
همانطور که ما درخواست کردیم که عنصر وبلاگ با استفاده از واردات HTML (روشی برای گنجاندن و استفاده مجدد اسناد HTML در اسناد HTML دیگر) به فهرست خود وارد شود، همچنین میتوانیم تأیید کنیم که به درستی به سند <head>
اضافه شده است:
<!doctype html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="styles/main.css">
<!-- build:js scripts/vendor/modernizr.js -->
<script src="bower_components/modernizr/modernizr.js"></script>
<!-- endbuild -->
<!-- Place your HTML imports here -->
<link rel="import" href="elements/blog.html">
</head>
<body>
<div class="container">
<div class="hero-unit" style="width:90%">
<blog-element></blog-element>
</div>
</div>
<script>
document.addEventListener('WebComponentsReady', function() {
// Perform some behaviour
});
</script>
<!-- build:js scripts/vendor.js -->
<script src="bower_components/polymer/polymer.min.js"></script>
<!-- endbuild -->
</body>
</html>
فوق العاده.
افزودن وابستگی ها با استفاده از Bower
در مرحله بعد، بیایید عنصر خود را ویرایش کنیم تا از عنصر کاربردی Polymer JSONP برای خواندن در posts.json استفاده کنیم. میتوانید آداپتور را با شبیهسازی git مخزن یا نصب polymer-elements
از طریق Bower با اجرای bower install polymer-elements
دریافت کنید.
هنگامی که این ابزار را دارید، باید آن را به عنوان یک import در عنصر blog.html خود قرار دهید با:
<link rel="import" href="../bower_components/polymer-jsonp/polymer-jsonp.html">
سپس، برچسب مربوط به آن را اضافه کنید و url
به صفحه گسترده پستهای وبلاگ ما از قبل ارائه دهید، و &callback=
به انتها اضافه کنید:
<polymer-jsonp auto url="https://spreadsheets.google.com/feeds/list/your-key-value/od6/public/values?alt=json-in-script&callback=" response="[[posts]]"></polymer-jsonp>
با وجود این، اکنون میتوانیم الگوهایی را اضافه کنیم تا پس از خواندن صفحهگستردهمان، آنها را تکرار کنند. صفحه اول فهرستی از محتویات را به همراه یک عنوان پیوندی برای یک پست نشان میدهد که به حلزون آن اشاره میکند.
<!-- Table of contents -->
<ul>
<template repeat="[[post in posts.feed.entry]]">
<li><a href="#[[post.gsx$slug.$t]]">[[post.gsx$title.$t]]</a></li>
</template>
</ul>
دومی برای هر ورودی یافت شده، یک نمونه از post-element
ارائه میکند و محتوای پست را بر این اساس به آن ارسال میکند. توجه داشته باشید که ما در حال عبور از یک ویژگی post
هستیم که نشان دهنده محتوای پست برای یک ردیف صفحه گسترده و یک ویژگی selected
که با یک مسیر پر می کنیم.
<!-- Post content -->
<template repeat="[[post in posts.feed.entry]]">
<post-element post="[[post]]" selected="[[route]]"></post-element>
</template>
ویژگی repeat
که مشاهده میکنید در قالب ما استفاده میشود، یک نمونه با [[ bindings ]] را برای هر عنصر در مجموعه آرایههای پستهای ما، زمانی که ارائه میشود، ایجاد و نگهداری میکند.
اکنون برای اینکه بتوانیم [[route]] فعلی را پر کنیم، میخواهیم تقلب کنیم و از کتابخانهای به نام Flatiron Director استفاده کنیم که هر زمان که هش URL تغییر کند به [[route]] متصل میشود.
خوشبختانه یک عنصر پلیمر (بخشی از بسته عناصر بیشتر ) وجود دارد که میتوانیم آن را بگیریم. هنگامی که در دایرکتوری /elements کپی شد، میتوانیم آن را با <flatiron-director route="[[route]]" autoHash></flatiron-director>
ارجاع دهیم، و route
بهعنوان خاصیتی که میخواهیم به آن متصل شود مشخص کنیم و به آن بگوییم که به طور خودکار به آن متصل شود. مقدار هر گونه تغییر هش (autoHash) را بخوانید.
با کنار هم گذاشتن همه چیز، اکنون دریافت می کنیم:
<link rel="import" href="post.html">
<link rel="import" href="polymer-jsonp/polymer-jsonp.html">
<link rel="import" href="flatiron-director/flatiron-director.html">
<polymer-element name="blog-element" attributes="">
<template>
<style>
@host { :scope {display: block;} }
</style>
<div class="row">
<h1><a href="/#">My Polymer Blog</a></h1>
<flatiron-director route="[[route]]" autoHash></flatiron-director>
<h2>Posts</h2>
<!-- Table of contents -->
<ul>
<template repeat="[[post in posts.feed.entry]]">
<li><a href="#[[post.gsx$slug.$t]]">[[post.gsx$title.$t]]</a></li>
</template>
</ul>
<!-- Post content -->
<template repeat="[[post in posts.feed.entry]]">
<post-element post="[[post]]" selected="[[route]]"></post-element>
</template>
</div>
<polymer-jsonp auto url="https://spreadsheets.google.com/feeds/list/0AhcraNy3sgspdHVQUGd2M2Q0MEZnRms3c3dDQWQ3V1E/od6/public/values?alt=json-in-script&callback=" response="[[posts]]"></polymer-jsonp>
</template>
<script>
Polymer('blog-element', {
created: function() {},
enteredView: function() { },
leftView: function() { },
attributeChanged: function(attrName, oldVal, newVal) { }
});
</script>
</polymer-element>
وو ما اکنون یک وبلاگ ساده داریم که دادهها را از JSON میخواند و از دو عنصر پلیمری با داربست Yeoman استفاده میکند.
کار با عناصر شخص ثالث
اکوسیستم عناصر پیرامون Web Components اخیراً با سایتهای گالری مؤلفههایی مانند customelements.io در حال رشد است. با نگاهی به عناصر ایجاد شده توسط انجمن، یکی را برای واکشی نمایههای گراواتار پیدا کردم و در واقع میتوانیم آن را بگیریم و به سایت وبلاگ خود اضافه کنیم.
منابع عنصر گراواتار را در دایرکتوری /elements
کپی کنید، آن را از طریق imports HTML در post.html اضافه کنید و سپس اضافه کنید.
<link rel="import" href="gravatar-element/src/gravatar.html">
<polymer-element name="post-element" attributes="post selected">
<template>
<style>
@host { :scope {display: block;} }
</style>
<div class="col-lg-4">
<template if="[[post.gsx$slug.$t === selected]]">
<h2><a href="#[[post.gsx$slug.$t]]">[[post.gsx$title.$t]]</a></h2>
<p>By [[post.gsx$author.$t]]</p>
<gravatar-element username="[[post.gsx$email.$t]]" size="100"></gravatar-element>
<p>[[post.gsx$content.$t]]</p>
<p>[[post.gsx$date.$t]]</p>
<small>Keywords: [[post.gsx$keywords.$t]]</small>
</template>
</div>
</template>
<script>
Polymer('post-element', {
created: function() { },
enteredView: function() { },
leftView: function() { },
attributeChanged: function(attrName, oldVal, newVal) { }
});
</script>
</polymer-element>
بیایید نگاهی بیندازیم به آنچه که این به ما می دهد:
زیبا!
در مدت زمان نسبتاً کوتاهی، ما یک برنامه ساده متشکل از چندین مؤلفه وب ایجاد کردهایم، بدون اینکه نگران نوشتن کد دیگ بخار، دانلود دستی وابستگیها یا راهاندازی یک سرور محلی یا ساخت گردش کار باشیم.
بهینه سازی اپلیکیشن شما
گردش کار Yeoman شامل پروژه منبع باز دیگری به نام Grunt است - یک task runner که می تواند تعدادی از وظایف خاص ساخت (تعریف شده در Gruntfile) را برای تولید یک نسخه بهینه از برنامه شما اجرا کند. اجرای grunt
به خودی خود یک وظیفه default
را که ژنراتور برای پر کردن، آزمایش و ساخت تنظیم کرده است، اجرا میکند:
grunt.registerTask('default', [
'jshint',
'test',
'build'
]);
وظیفه jshint
در بالا با فایل .jshintrc
شما بررسی می شود تا تنظیمات برگزیده شما را بیاموزد، سپس آن را با تمام فایل های جاوا اسکریپت در پروژه شما اجرا می کند. برای دریافت کامل گزینه های خود با JSHint، اسناد را بررسی کنید.
تکلیف test
کمی شبیه به این است و میتواند برنامه شما را برای چارچوب آزمایشی که ما خارج از جعبه توصیه میکنیم، Mocha ایجاد کرده و ارائه دهد. همچنین تست های شما را برای شما اجرا می کند:
grunt.registerTask('test', [
'clean:server',
'createDefaultTemplate',
'jst',
'compass',
'connect:test',
'mocha'
]);
از آنجایی که برنامه ما در این مورد نسبتاً ساده است، تست های نوشتن را به عنوان یک تمرین جداگانه به شما واگذار می کنیم. چند چیز دیگر وجود دارد که برای مدیریت فرآیند ساختمان به آن نیاز داریم، بنابراین بیایید نگاهی به کار grunt build
تعریف شده در Gruntfile.js
داشته باشیم:
grunt.registerTask('build', [
'clean:dist', // Clears out your .tmp/ and dist/ folders
'compass:dist', // Compiles your Sassiness
'useminPrepare', // Looks for <!-- special blocks --> in your HTML
'imagemin', // Optimizes your images!
'htmlmin', // Minifies your HTML files
'concat', // Task used to concatenate your JS and CSS
'cssmin', // Minifies your CSS files
'uglify', // Task used to minify your JS
'copy', // Copies files from .tmp/ and app/ into dist/
'usemin' // Updates the references in your HTML with the new files
]);
grunt build
اجرا کنید و یک نسخه آماده تولید از برنامه شما باید ساخته شود و آماده ارسال باشد. بیایید آن را امتحان کنیم.
موفقیت!
اگر گیر کردید، نسخه از پیش ساخته شده پلیمر بلاگ در دسترس شما است تا از https://github.com/addyosmani/polymer-blog دیدن کنید.
چه چیز دیگری در انبار داریم؟
اجزای وب هنوز در حالت تکامل هستند و به همین ترتیب ابزارهای پیرامون آنها نیز همینطور است.
ما در حال حاضر در حال بررسی این موضوع هستیم که چگونه میتوان واردات HTML خود را برای بهبود عملکرد بارگذاری از طریق پروژههایی مانند Vulcanize (ابزاری توسط پروژه پلیمر) به هم متصل کرد و چگونه اکوسیستم کامپوننتها ممکن است با مدیر بستهای مانند Bower کار کند.
به محض اینکه پاسخهای بهتری برای این سؤالات داشته باشیم، به شما اطلاع خواهیم داد، اما زمانهای هیجانانگیزی در پیش است.
نصب پلیمر به صورت مستقل با Bower
اگر استارت سبکتری را به پلیمر ترجیح میدهید، میتوانید آن را مستقیماً از Bower با اجرای زیر نصب کنید:
bower install polymer
که آن را به فهرست راهنمای bower_components شما اضافه می کند. سپس می توانید آن را به صورت دستی در فهرست برنامه خود ارجاع دهید و آینده را تکان دهید.
نظر شما چیست؟
اکنون میدانید که چگونه با استفاده از Web Components با Yeoman، یک برنامه Polymer را داربستبندی کنید. اگر بازخوردی در مورد ژنراتور دارید، لطفاً در نظرات به ما اطلاع دهید یا یک اشکال ارسال کنید یا در ردیاب مشکل Yeoman پست کنید. ما دوست داریم بدانیم که آیا چیز دیگری وجود دارد که مایلید عملکرد بهتر ژنراتور را ببینید، زیرا فقط از طریق استفاده و بازخورد شما است که میتوانیم بهبود ببخشیم :)