<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>پست‌های انتشارات Xeniac</title>
        <link>https://virgool.io/Xeniac/feed</link>
        <description>تیم طراحی و توسعه زنیاک</description>
        <language>fa</language>
        <pubDate>2026-06-16 16:30:48</pubDate>
        <image>
            <url>https://files.virgool.io/upload/publication/ykvixg3ice5e/ilip4x.png</url>
            <title>Xeniac</title>
            <link>https://virgool.io/Xeniac</link>
        </image>

                    <item>
                <title>برای دوست عزیزمان، محمدمهدی فخرالاسلام</title>
                <link>https://virgool.io/Xeniac/%D8%A8%D8%B1%D8%A7%DB%8C-%D8%AF%D9%88%D8%B3%D8%AA-%D8%B9%D8%B2%DB%8C%D8%B2%D9%85%D8%A7%D9%86-%D9%85%D8%AD%D9%85%D8%AF%D9%85%D9%87%D8%AF%DB%8C-%D9%81%D8%AE%D8%B1%D8%A7%D9%84%D8%A7%D8%B3%D9%84%D8%A7%D9%85-epk8ffamktgf</link>
                <description>با قلبی پر از اندوه، متأسفانه (15 بهمن 1403) محمدمهدی فخرالاسلام، یکی از اعضای تیم زنیاک، برای همیشه از میان ما رفت. دوستی که نه‌تنها در طراحی و برنامه‌نویسی استعداد کم‌نظیری داشت، بلکه اخلاق حرفه‌ای و منش والایش زبانزد تمام کسانی بود که افتخار همکاری با او را داشتند.برای دوست عزیزمان، محمدمهدی فخرالاسلاممهدی در ۳۰ مهر ۱۳۷۷ در شهر کمیجان، استان مرکزی به دنیا آمد و تحصیلات خود را در دانشگاه فنی امیرکبیر اراک در رشته مهندسی نرم‌افزار به پایان رساند. و پس از فارغ‌التحصیلی، دوران خدمت سربازی‌اش را در نیروی زمینی ارتش سپری کرد. او همچنین موفق به کسب رتبه‌ی هشتم المپیاد کشوری کامپیوتر نیر شد.حادثه‌ای که باورش سخت استمهدی در شبی تلخ، در یکی از خیابان‌های مرکز شهر اراک، در یک سانحه‌ی تصادف رانندگی جان خود را از دست داد. او که سوار موتور بود، با دو خودرو تصادف کرد. بعد از انتقال به بیمارستان و خارج شدن از اتاق عمل، متأسفانه به دلیل خون‌ریزی داخلی، تنها یک ساعت پس از حادثه، از میان ما رفت... باورش سخت است که او دیگر در کنار ما نیست.از روزهای اول دوستی تا پروژه‌های بزرگآشنایی ما به سال ۱۳۹۲ و دوران دبیرستان (هنرستان قائم اراک، رشته کامپیوتر) برمی‌گردد. از همان ابتدای مسیر، با هم ساخت پروژه‌های مختلفی را شروع کردیم:زون گیم، که اولین وب‌سایت بازی ما بود.آی‌میون، یک فروشگاه سی‌ دی کی دیجیتال.و در نهایت زنیاک، که آغاز فعالیت حرفه‌ای ما در قالب یک تیم ۶ نفره در دنیای توسعه نرم‌افزار بود.در کنار این‌ها، مهدی روی پروژه‌های متن‌باز (Open Source) متعددی کار کرد که برخی از آن‌ها شامل:پکیج persian-tools: مجموعه‌ای از ابزارهای کاربردی برای زبان فارسی در جاوا اسکریپت.پکیج xmodal: یک مدال سبک برای پروژه‌های Vue.js. پکیج vue-input-validator: یک سیستم قدرتمند برای اعتبارسنجی داده‌ها در Vue.نرم افزار chill-club: استریم ۲۴/۷ موسیقی آرامش‌بخش برای گیمینگ، کدنویسی و مطالعه.نرم افزار Foxhole artillery: یک ماشین حساب ساده برای استفاده از هویتزر، توپخانه صحرایی، قایق‌های توپدار و خمپاره برای بازی foxhole.و بسیاری دیگر.در همین روزها بود که قرار بود کار روی نسخه‌ی جدید وب‌سایت زنیاک را آغاز کنیم. ایده‌های بزرگی در سر داشتیم و آماده بودیم تا قدم‌های تازه‌ای برداریم، اما حالا با رفتن مهدی، نه‌تنها یکی از بهترین دوستان و همکارانمان را از دست دادیم، بلکه مسیر پیش رو هم برایمان تاریک شده است. ادامه دادن بدون او سخت خواهد بود... میراث مهدیکارهای ارزشمند مهدی را می‌توانید در لینک‌های زیر مشاهده کنید:🌐 Website🔗 Dribbble💻 GitHub👔 LinkedInنوشتن این پست، تنها راهی بود که می‌توانستیم گوشه‌ای از عشق و احترام‌مان به مهدی را نشان دهیم. اما حقیقت این است که هیچ واژه‌ای نمی‌تواند جای خالی او را پر کند.از طرف تمام دوستان دوران مدرسه، دانشگاه و همکاران.دلمان برایت خیلی تنگ خواهد شد، مهدی جان... 🕊💔</description>
                <category>Xeniac</category>
                <author>یوسف روشندل</author>
                <pubDate>Wed, 05 Feb 2025 22:05:04 +0330</pubDate>
            </item>
                    <item>
                <title>بهترین شیوه‌های Node.js: راهنمایی برای توسعه‌دهندگان</title>
                <link>https://virgool.io/Xeniac/nodejs-best-practices-a-guide-for-developers-s49iqwetlwxd</link>
                <description>نود جی‌اس (Node.js) یک ابزار قدرتمند برای ساخت برنامه‌های وب مقیاس‌پذیر و سریع است. با این حال، برای استفاده حداکثری از Node.js، پیروی از بهترین شیوه‌ها مهم است. در این پست، برخی از بهترین شیوه‌های کلیدی برای توسعه نرم افزارهای Node.js را بررسی خواهیم کرد.بهترین شیوه‌های Node.js: راهنمایی برای توسعه‌دهندگان 1. به پروژه خود Structure (ساختار) دهیدنگهداری و توسعه یک پروژه با ساختار مناسب به مراتب راحت‌تر از پروژه‌ای هست که ساختار استاندارد و مناسبی ندارد. در زیر یک Structure ساده آماده کرده‌ام که از آن میتوانید در پروژه های Node.js تان استفاده کنید:my-node-app/
│
├── src/
│   ├── controllers/
│   ├── models/
│   ├── routes/
│   └── services/
│
├── tests/
│
├── .env
├── .gitignore
├── package.json
└── README.mdsrc/شامل کد اصلی برنامه شماست.── controllers/مدیریت لاجیک برنامه.── models/تعریف ساختار داده‌ها.── routes/مدیریت endpoint های API ها.── services شامل بیزنس لاجیک‌ها.tests/پوشه مخصوص برای فایل‌های تست پروژه..envذخیره سازی تمامی environment variable ها..gitignoreمشخص کننده فایل های git که باید نادیده گرفته شوند..package.jsonاسکریپت‌ها و Dependency های پروژه .README.mdشرح و توضیحات مربوط به پروژه.2. از Environment Variable ها استفاده کنیدبا استفاده از Environment Variable ها می‌توانید تنظیمات پیکربندی‌های مربوط به پروژ را خارج از کدهایتان ذخیره کنید که این کار علاوه بر اینکه برنامه شمارا امن‌تر می‌کند، بلکه مدیریت آن‌را نیز ساده‌تر خواهد کرد.نمونه یک فایل .env:DB_HOST=localhost
DB_USER=root
DB_PASS=passwordبا استفاده از dotenv می‌توانید فایل .env را در کدتان لود و استفاده کنید:require(&#039;dotenv&#039;).config();

const dbHost = process.env.DB_HOST;
const dbUser = process.env.DB_USER;
const dbPass = process.env.DB_PASS;

console.log(`Connecting to database at ${dbHost} with user ${dbUser}`);3. ارورها را به درستی مدیریت کنیدمدیریت صحیح ارورها تضمین می‌کند که برنامه شما بطور ناگهانی کرش نخواهد کرد.app.get(&#039;/user/:id&#039;, async (req, res, next) =&gt; {
  try {
    const user = await getUserById(req.params.id);
    if (!user) {
      return res.status(404).send(&#039;User not found&#039;);
    }
    res.send(user);
  } catch (error) {
    next(error);
  }
});

app.use((err, req, res, next) =&gt; {
  console.error(err.stack);
  res.status(500).send(&#039;Something went wrong!&#039;);
});4. از کد Asynchronous استفاده کنیدنود جی‌اس ذاتاً asynchronous (ناهمگام یا نامتقارن) است. از async و await بری هندل کردن asynchronous کدتان استفاده کنید.async function fetchData() {
  try {
    const response = await fetch(&#039;https://api.example.com/data&#039;);
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error(&#039;Error fetching data:&#039;, error);
  }
}

fetchData();5. پکیج‌ها و Dependency های پروژه را آپدیت نگه داریدبطور منظم پکیج‌ها و Dependency ها را به روز کنید تا مطمئن شوید که آخرین ویژگیها و اصلاحات امنیتی را دارید.می‌توانید با استفاده از دستور زیر پکیج‌های قدیمی پروژه را چک کنید:npm outdatedو سپس با دستور زیر آن‌ها را آپدیت کنید:npm update6. تست بنویسدنوشتن تست از رخ دادن بسیاری از باگ‌ها جلوگیری می‌کند و به شما اطمینان می‌دهند که برنامه طبق انتظارتان کار می‌کند.مرحله 1: نصب Jestیکی از محبوب‌ترین فریم‌ورک‌های قدرتمند برای تست کردن کدهای جاوااسکریپت Jest است که با دستور زیر می‌توانید آن‌را نصب کنید:npm install --save-dev jestمرحله 2: نوشتن تست‌هایک فایل تست ایجاد کنید. برای مثال (tests/example.test.js).یک مثال تست نویسی ساده:const sum = (a, b) =&gt; a + b;

test(&#039;adds 1 + 2 to equal 3&#039;, () =&gt; {
  expect(sum(1, 2)).toBe(3);
});7. از یک Linter استفاده کنیدLinter ابزاری که با آنالیز کردن کد منبع شما، هرگونه خطای برنامه نویسی، اشکالات، خطاهای سبکی و ساختارهای مشکوک را علامت گذاری کند. و در نهایت کدهای خود را تمیز و ثابت نگه دارید.ESLint یکی از محبوب‌ترین گزینه‌ها در میان برنامه نویس‌های جاوااسکریپت است.نصب ESLint:npm install eslint --save-devراه اندازی ESLint:npx eslint --initاضافه کردن lint به package.json پروژه:&amp;quotscripts&amp;quot: {
  &amp;quotlint&amp;quot: &amp;quoteslint .&amp;quot
}اجرای linter:npm run lintنتجیه‌گیریاستفاده از این روش‌ها به شما کمک می‌کند تا بتوانید نرم افزارهای Node.js بهتری بنویسید که قابلیت نگهداری بیشتری نیز دارند. به خاطر داشته باشید که پروژه خود را ساختار دهید، از environmental variable ها استفاده کنید، خطاها را به درستی مدیریت کنید، کدهای asynchronous بنویسید، dependency ها را به‌روز نگه دارید، تست بنویسید و از linter استفاده کنید.با انجام این کارا نرم افزاهای Node.js تان علاوه بر اینکه قوی‌تر و کارآمد‌تر خواهد شد، بلکه مدیریت و نگهداری آن نیز به مراتب راحت‌تر خواهد بود.امیدوارم که این مقاله براتون مفید بوده باشه.- منبع</description>
                <category>Xeniac</category>
                <author>یوسف روشندل</author>
                <pubDate>Sat, 13 Jul 2024 17:22:11 +0330</pubDate>
            </item>
                    <item>
                <title>پیاده سازی reactivity فریمورک ویو در جاواسکریپت</title>
                <link>https://virgool.io/Xeniac/%D9%BE%DB%8C%D8%A7%D8%AF%D9%87-%D8%B3%D8%A7%D8%B2%DB%8C-reactivity-%D9%81%D8%B1%DB%8C%D9%85%D9%88%D8%B1%DA%A9-%D9%88%DB%8C%D9%88-%D8%AF%D8%B1-%D8%AC%D8%A7%D9%88%D8%A7%D8%B3%DA%A9%D8%B1%DB%8C%D9%BE%D8%AA-lyhw73sypply</link>
                <description>توی این آموزش قصد داریم تا با هم از صفر یک سیستم دیتا reactivity رو در جاواسکریپت پیاده سازی کنیم.چرا reactivity?براتون یه مثال میزنم، کد زیر رو در نظر بگیریدconst price = 100
const fee = 10

const total = () =&gt; {
    return price + fee
}

render(total()) // 110

// new value
price = 300
render(total()) // 310همونطوری که میبینید اگر ما بخوایم بدون استفاده از reactivity یک مقدار رو آپدیت کنیم، باید این کار رو به صورت دستی انجام بدیم و در هر مرحله مثل کد بالا، متد های خودمون رو صدا بزنیماما reactivity به ما کمک میکنه این پروسه رو اتوماتیک کنیم و اجازه بدیم روند برنامه صدا زدن متد ها و آپدیت داده های ما رو به عهده بگیره.خروجی این آموزش به این صورت خواهد بودخروجی نهاییری اکتیویتی در ویو جی استو این آموزش سعی میکنیم تا سیستم شبیه به فریم ورک vue رو پیاده سازی کنیم ( نسخه 2 )ما میخوایم ورودی شبیه به کد زیر داشته باشیم ( درست مثل ویو )const App = Observable({
    data() {
        return {
            price: 300,
            fee: 10,
        }
    },
    computed: {
        totalSpend() {
            return this.price + this.fee
        },
    },

    methods: {
        sayHello() {
            console.log(&amp;quothello world&amp;quot)
        },
    },
})خب برای شروع متد Observable رو تعریف میکنیمconst Observable = (input) =&gt; {
    const deps = {}
    let effect = null
    const _app = {
        ...input.data(),
    }
}از _deps برای نگه داری وابستگی های متغیر ها به متد های computed استفاده میشهاز effect برای نگهداری computed فعال و در حال اجرا استفاده میشهاز _app به عنوان ورودی برنامه که به صورت دیفالت متغیر های داخل آبجکت Data رو داخلش میریزیم، استفاده میشهبرای ری اکتیو کردن داده ها، در جاوااسکریپت API وجود داره به اسمProxy. این API به شما اجازه میده درخواست های ارسالی به آبجکت رو شخصی سازی کنید. ما از این API برای خوندن و نوشتن داده توی متغیر هامون استفاده میکنیمما به 2 تا Proxy نیاز خواهیم داشت. یکی برای گوش کردن به تغییرات دیتا در ابجکت _app و دیگری برای گوش کردن به تغییرات computed هاابتدا با دیتا شروع میکنیم:const Observable = (input) =&gt; {
    const deps = {}
    let effect = null
    const _app = {
        ...input.data(),
    }

    // base proxy
    const _appProxy = new Proxy(_app, {
        get(target, key) {
            return target[key]
        },
        set(target, key, value) {
            target[key] = value
            return true
        },
    })

    return _appProxy
}در مرحله بعد Method هامون رو به آبجکت _app اضافه میکنیم// init methods
_app.methods = {}
Object.keys(input.methods).forEach((key) =&gt; {
    _app.methods[key] = input.methods[key].bind(_app)
})تو اینجا از input.methods[key].bind(_app) استفاده شده. برای زمانی هست که وقتی از this استفاده کردیم رفرنس رو به ابجکت _app از دست ندیمتو مرحله بعد computed هامون رو اضافه میکنیم// init computed -- convert to getters
Object.keys(input.computed).forEach((key) =&gt; {
    Object.defineProperty(_appProxy, key, {
        get() {
            const res = effect.call(_appProxy)
            return res
        },
    })
})توی اینجا به جای Proxy از DefineProperty علت خاصی نداره فقط میخواستم نشون بدم از هر دو روش میشه این کار رو انجام داد. این کد به ما کمک میکنه، زمانی که کاربر یکی از متد های computed رو صدا زد، ما به اون فاکنشن دسترسی داشته باشیم اضافه کردن reactivity به computed هاما نیاز به مکانیزمی داریم تا بتونیم تشخیص بدیم که computed های ما چه متغیر هایی رو اجرا میکنن و بعد از تشخیص، این computed ها رو به عنوان dependency به متغیر هامون اضافه میکنیم تا در صورت تغییر مقدار هر کدوم از متغیر ها، computed هامون رو دوباره اجرا بکنیمقطعه کد زیر رو نظر بگیریدconst App = Observable({
    data() {
        return {
            price: 300,
        }
    },
    computed: {
        getPrice() {
            return this.price
        },
    },
})

App.getPrice // 300اگر کد بالا رو اجرا بکنیم، Getter ای که در داخل فاکنشن Obseravable تعریف کردیم اجرا میشه. ما میدونیم که توی جاوااسکریپت در هر لحظه فقط یک دستور میتونه اجرا بشه پس: وقتی که getPrice اجرا میشه، ما باید اون رو به عنوان effect درحال اجرا در نظر بگیریمپس کد بخش computed ما به این صورت تغییر میکنه:// init computed -- convert computeds to getters
Object.keys(input.computed).forEach((key) =&gt; {
    Object.defineProperty(_appProxy, key, {
        get() {
            effect = input.computed[key]
            const res = effect.call(_appProxy)
            effect = null
            return res
        },
    })
})قبل از اجرا شدن متد computed ما، اون رو در درون متغیر effect قرار میدیم و بعد از اتمام اجرا، اون رو به null تغییر میدیمبه این نکته توجه کنید وقتی که computed ما اجرا میشه effect.call(_appProxy) چون در داخل این متد از this.price استفاده کردیم، این باعث میشه Proxy ما برای دیتا اجرا بشهاز اونجایی که ما effect و متد فعال فعلی رو ذخیره داریم، میتونیم این متد رو به عنوان dependency به متغیر ها اضافه کنیمپس بخش get ما به این صورت تغییر میکنه// base proxy
const _appProxy = new Proxy(_app, {
    get(target, key) {
        if (!deps[key]) {
            deps[key] = new Set()
        }
        effect &amp;&amp; deps[key].add(effect)
        return target[key]
    },
    // --------------
})ابتدا چک میکنم تا ببینیم متغیر فعلی دارای در داخل ابجکت deps وجود داره یا نه، اگر وجود نداشت اون رو ایجاد میکنیم. علت استفاده از new Set() به این خاطر هست که ما نمیخوایم یک فاکنشن چند بار اجرا بشهبعد از ذخیره deps ها ما همچین ساختاری رو خواهیم داشتconsole.log(deps)
/*  {
        price:  getPrice() { return this.price; }
    }
*/حالا اگر هرکدوم از این دیتا های ما تغییر بکنند، ما dependency های اون را دونه به دونه صدا میزنیمبرای اینکه متوجه بشیم آیا تغییر کردنند یا نه از set در proxy استفاده میکنیم کد ما به این صورت تغییر میکنه:// base proxy
const _appProxy = new Proxy(_app, {
    // ----------------
    set(target, key, value) {
        target[key] = value
        const _deps = deps[key]
        if (_deps) {
            _deps.forEach((effect) =&gt; {
                return effect.call(_appProxy)
            })
        }
        return true
    },
})همونطور که میبینیدconst _deps = deps[key]
if (_deps) {
    _deps.forEach((effect) =&gt; {
        return effect.call(_appProxy)
    })
}همه ی deps ها رو اجرا میکنیم تا مطلع بشن که دیتای ما تغییر کردهاگر تا اینجا دنبال کرده باشید کد ما به این صورت خواهد بودconst Observable = (input) =&gt; {
    const deps = {}
    let effect = null
    const _app = {
        ...input.data(),
    }

    // base proxy
    const _appProxy = new Proxy(_app, {
        get(target, key) {
            if (!deps[key]) {
                deps[key] = new Set()
            }
            effect &amp;&amp; deps[key].add(effect)
            return target[key]
        },
        set(target, key, value) {
            target[key] = value
            const _deps = deps[key]
            if (_deps) {
                _deps.forEach((effect) =&gt; {
                    return effect.call(_appProxy)
                })
            }
            return true
        },
    })

    // computed proxy
    // init methods
    _app.methods = {}
    Object.keys(input.methods).forEach((key) =&gt; {
        _app.methods[key] = input.methods[key].bind(_app)
    })

    // init computed -- convert computeds to getters
    Object.keys(input.computed).forEach((key) =&gt; {
        Object.defineProperty(_appProxy, key, {
            get() {
                effect = input.computed[key]
                const res = effect.call(_appProxy)
                effect = null
                return res
            },
        })
    })
    return _appProxy
}خب کار ما اینجا تموم میشه. برای اینکه ببینیم کدی که زدیم کار میکنه یا نه یه پروژه کوچیک با هم انجام میدیمیک ابجکت از کلاس Observable مون میسازیم با ورودی های زیر:const App = Observable({
    data() {
        return {
            price: 300,
            fee: 10,
        }
    },
    computed: {
        totalSpend() {
            return this.price + this.fee
        },
    },

    methods: {
        price_up() {
            this.price++
        },
        price_down() {
            this.price--
        },

        fee_up() {
            this.fee++
        },
        fee_down() {
            this.fee--
        },
    },
})یک فاکنشن تعریف میکنیم به اسم render وظیفه این فاکنشن اضافه کردن کد html یک جدول به صفحه ماست (مثل گیف پروژه نهایی در بتدای پروژه)const render = () =&gt; {
    const appElem = document.getElementById(&amp;quotapp&amp;quot)
    appElem = `
    &lt;table class=&amp;quottable&amp;quot&gt;
            &lt;tbody&gt;
                &lt;tr&gt;
                    &lt;td class=&amp;quotbold&amp;quot&gt;&lt;p&gt;product price&lt;/p&gt;&lt;/td&gt;
                    &lt;td class=&amp;quotprice bold&amp;quot &gt;${App.price}$&lt;/td&gt;
                    &lt;td&gt;
                        &lt;div class=&amp;quotbtn-group-vertical&amp;quot role=&amp;quotgroup&amp;quot aria-label=&amp;quotFirst group&amp;quot&gt;
                            &lt;button type=&amp;quotbutton&amp;quot class=&amp;quotbtn btn-secondary&amp;quot @click=&amp;quotprice_up&amp;quot&gt;+&lt;/button&gt;
                            &lt;button type=&amp;quotbutton&amp;quot class=&amp;quotbtn btn-secondary&amp;quot @click=&amp;quotprice_down&amp;quot&gt;-&lt;/button&gt;
                        &lt;/div&gt;
                    &lt;/td&gt;
                &lt;/tr&gt;
                &lt;tr&gt;
                    &lt;td class=&amp;quotbold&amp;quot&gt;Fee&lt;/td&gt;
                    &lt;td class=&amp;quotprice bold&amp;quot&gt;${App.fee}%&lt;/td&gt;
                    &lt;td&gt;
                        &lt;div class=&amp;quotbtn-group-vertical&amp;quot role=&amp;quotgroup&amp;quot aria-label=&amp;quotFirst group&amp;quot&gt;
                            &lt;button type=&amp;quotbutton&amp;quot class=&amp;quotbtn btn-secondary&amp;quot @click=&amp;quotfee_up&amp;quot&gt;+&lt;/button&gt;
                            &lt;button type=&amp;quotbutton&amp;quot class=&amp;quotbtn btn-secondary&amp;quot @click=&amp;quotfee_down&amp;quot&gt;-&lt;/button&gt;
                        &lt;/div&gt;
                    &lt;/td&gt;
                &lt;/tr&gt;
                &lt;tr&gt;
                    &lt;td class=&amp;quotbold&amp;quot&gt;Total&lt;/td&gt;
                    &lt;td class=&amp;quotprice bold&amp;quot&gt;${App.totalSpend}$&lt;/td&gt;
                    &lt;td&gt;&lt;/td&gt;
                &lt;/tr&gt;
            &lt;/tbody&gt;
        &lt;/table&gt;
    `
}

render()همونطور که میبینید در داخل جدول از String Template ها استفاده شده و داده هامون رو قراره نشون بده&lt;td class=&amp;quotprice bold&amp;quot&gt;${App.fee}%&lt;/td&gt;همچنین مثل فریمورک vue از custom attribute ها استفاده کردیم تا بتونیم به click event گوش کنیم&lt;button @click=&amp;quotfee_up&amp;quot&gt;+&lt;/button&gt; &lt;button @click=&amp;quotfee_down&amp;quot&gt;-&lt;/button&gt;خب حالا به اونت کلیک روی صفحه گوش میدیم و برسی میکنیم که کاربر کجا کلیک کرده اگر روی المنت هایی که click attribute دارن کلیک کرده باشه، فاکنشن ها رو اجرا میکنیمdocument.addEventListener(&amp;quotclick&amp;quot, (e) =&gt; {
    const customClickAttr = e.target.attributes
    // get click attribute from list of attributes
    const clickAttr = customClickAttr.getNamedItem(&amp;quot@click&amp;quot)
    if (clickAttr) {
        const methodName = clickAttr.value
        App.methods[methodName]()
        render()
    }
})بعد از اینکه دیتامون رو اپدیت کردیم نیاز هست تا صفحه رو هم آپدیت کنیم. به خاطر همین بعد از اجرا متد هامون، متد render رو دوباره صدا میزنیمخب پروژه ما اینجا تموم میشه، برای تمرین اگر دوست داشتید سعی کنید مثل Vue قابلیت Watcher و گوش کردن به همه تغییرات متغیر ها رو پیاده کنید.اگر این آموزش براتون جالب بود میتونید کد های این پروژه روی گیت هاب مشاهده کنید[سورس کد پروژه]همچنین میتونید این مطلب رو در بلاگ شخصی من بخونید https://mediv.vercel.app/blog/data-reactivity-in-javascript HAPPY CODING</description>
                <category>Xeniac</category>
                <author>Mahdi fakhr</author>
                <pubDate>Thu, 17 Mar 2022 14:14:48 +0330</pubDate>
            </item>
                    <item>
                <title>وب اسکرپینگ (Web Scraping) با JavaScript و Node.js</title>
                <link>https://virgool.io/Xeniac/web-scraping-with-nodejs-fcbr0jyuqzoe</link>
                <description>جاوا اسکریپت با پیشرفت‌هایی که داشته و همچنین معرفی Node.js، حالا به یکی از محبوب‌ترین و پر استفاده‌ترین زبان‌های برنامه‌نویسی دنیا تبدیل شده. فرقی نمی‌کنه قصد توسعه نرم‌افزار وب داشته باشید و یا نرم افزار موبایل و دسکتاپ، جاوااسکریپت برای هر کاری کلی ابزار قدرتمند در اختیارتان قرار میده. حالا قصد دارم تو این مقاله بهتون نشون بدم که چطوری با استفاده از Node.js می‌تونید وب اسکرپینگ کنید.وب اسکرپینگ (Web Scraping) چیست؟قبل از اینکه بریم سراغ اصل مطلب، بهتره که خیلی خلاصه وب اسکرپینگ رو بهتون توضیح بدم:به فرآیند جمع‌آوری خودکار دیتاهای ساختار یافته وب، وب اسکرپینگ و یا استخراج داده‌های وب میگیم.و کاربردهای زیادی از جمله نظارت برروی قیمت‌ها و اخبار، جذب مخاطب، تحقیقات بازاریابی و... داره.روش‌های مختلفی برای اسکرپ کردن وجود داره که هر کدوم مزایا و معایت خاص خودشون رو دارن و من قصد دارم تو این مقاله اسکرپ کردن به روش‌های زیر رو توضیح بدم:JSON APIServer-Side Rendered HTMLJavaScript rendered HTMLو برای هر یک از این ۳ مورد،‌ من از وب‌سایت‌هایnba.com/statsespn.combasketball-reference.com(به ترتیب) استفاده می‌کنم. برای اینکه متوجه بشیم چه چیزی رو باید اسکرپ کنیم، چطوری با استفاده از network request ها API های مربوطه رو پیدا کنیم و چگونه با اسکریپت‌های Node.js، این فرآیند رو خودکار کنیم، لازمه که اول موارد رندر شده در صفحه رو بررسی کنیم.روش شماره ۱: JSON APIیکی از رایج‌ترین روش‌های استفاده شده در وب برای لود کردن دیتا،‌ استاده از درخواست‌‌های asynchronous از طرف جاوااسکریپت به API Server و دریافت داده‌ها با فرمت JSON است که بعد از رندر شدن، به کاربر نمایش داده می‌شن. در این روش، ما با استفاده از اسکریپت نوشته شده در Node.js، درخواست‌های API رو رهگیری می‌کنیم و سپس شروع به کار روی داده‌های JSON می‌کنیم. برای یادگیری این روش از وب‌سایت  nba.com/stats استفاده می‌کنیم.پیدا کردن درخواست‌های APIقراره اسکریپتی بنویسیم که اطلاعات مربوط به هر سال LeBron James (بازیکن بسکتبال) رو ذخیره کنه.مرحله ۱: مطلع شدن از داینامیک بودن داده‌هااز اونجایی که فقط میخوایم اطلاعات مروبط به LeBron James رو بگیریم، پس به آدرس nba.com/stats/player/2544 که صفحه این بازیکن میشه میریم.اسکرین شات از صفحه LeBron Jamesهمونطور که می‌بینید، تمام داده‌های مورد نظرمون تو این جدول وجود داره. اما قبل از هرکاری باید برسی کنیم و ببینیم که آیا دیتاها به‌صورت استاتیک در جدول HTML قرار داده شدن و یا به‌صورت داینامیک و از طریق JSON API لود شدن. برای انجام این کار، روی صفحه راست کلیک کنید و گزینه View Page Source رو بزنید و یا از میانبرهای Ctrl+U استفاده کنید.حالا تو صفحه سورس Ctrl+F بزنید و برای مثال عبارت &quot;2020-21&quot; رو سرچ کنید. اگه مثل تصویر پایین عبارتی که سرچ کردید تو سورس نبود، مطلع میشیم که دیتا به‌صورت داینامیک و از طریق JSON API لود شده.اسکرین شات از صفحه سورس LeBron Jamesمرحله ۲: پیدا کردن APIحالا که از داینامیک بودن دیتا‌ها مطمئن شدیم، وقشته بفهمیم که دیتا‌ها از کجا میان. برای این کار، روی جدول راست کلیک کنید و گزینه inspect رو بزنید و یا از میانبر Ctrl+Shift+I استفاده کنید.سورس مربوط به جدول HTMLخوشبختانه در تگ &lt;nba-stat-table&gt; یکسری سرنخ وجود داره که کمی تو این کار کمکمون می‌کنه.&lt;nba-stat-table
ng-if=&amp;quot!datasets.Base.isLoading &amp;&amp; !datasets.Base.noData&amp;quot
rows=&amp;quotdatasets.Base.sets[1].rows&amp;quot
params=&amp;quotparams&amp;quot
filters=&amp;quotfilters&amp;quot
ai=&amp;quotai&amp;quot
template=&amp;quotplayer/player-traditional&amp;quot &gt;همونطوری که مشاهده می‌کنید، جدول به datasets.Base و datasets.Base.sets[1].rows اشاره می‌کنه. حالا باید به دنبال network call هایی بگردیم که با درخواست‌های API تطابق دارن. برای انجام این کار، به تب Network می‌ریم و صفحه رو یکبار رفرش می‌کنیم تا تمام درخواست‌ها ثبت بشن.بعد از اینکه به تب Network رفتید و صفحه رو رفرش کردید، چیزایی که در تصویر زیر مشاهده می‌کنید رو شماهم باید در تب Network مرورگرتون مشاهده کنید. (بسته به مرورگتون ممکنه developer tools شما با مال من کمی متفاوت باشه که چیز مهمی نیست)تب Network مرورگر فایرفاکساینا تمام درخواست‌هایی هستند که مرورگر برای رندر کردن صفحه درست می‌کنه. حالا باید دنبال درخواست API مدنظرمون بگردیم. برای اینکار، روی آیکون XHR کلیک کنید و در کادر مربوط به سرچ، عبارت &quot;Base&quot; رو تایپ کنید.بعد از اعمال فیلتر،‌ از بین درخواست‌ها دنبال درخواستی بگردید که مانند تصویر زیر، در Response آن یکسری اطلاعات JSON برامون برگردونده باشه:playerdashboardyearoveryearریسپانس مربوط بهحالا با استفاده از سرنخ‌هایی که قبلا از جدولمون پیدا کردیم، می‌دونیم که برای پیدا کردن داده‌‌های جدول باید دنبال دیتاستی به اسم Base بگردیم. با کمی دقت به داده‌های Response متوجه می‌شیم که دومین آیتم موجود در resultSets با داده‌های جدول مطابقت داره و همین بهمون نشون میده که این همون درخواست API ای هست که دنبالشیم.دانلود داده‌های Response با استفاده از cURLحالا که می‌دوینم چطوری می‌تونیم داده‌هایی که برامون از اهمیت برخورد دارن رو به‌صورت دستی پیدا کنیم، وقشته که با استفاده از اسکریپت این کار رو خودکار انجام بدیم. در این مرحله قراره از ابزارهای curl و jq در ترمینال استفاده کنیم.برای این کار نیاز که jq روی سیستمون نصب باشه و اگه نصب نیست برید نصبش کنید.خب، حالا باز به تب Network برید و روی playerdashboardyearoveryear راست کلیک کنید و از منوی Copy گزینه Copy as cURL رو بزنید. الان تو کلیپ‌بورد سیستممون یه چیزی شبیه به دستور زیر رو داریم:curl &#039;https://stats.nba.com/stats/playerdashboardbyyearoveryear?DateFrom=&amp;DateTo=&amp;GameSegment=&amp;LastNGames=0&amp;LeagueID=00&amp;Location=&amp;MeasureType=Base&amp;Month=0&amp;OpponentTeamID=0&amp;Outcome=&amp;PORound=0&amp;PaceAdjust=N&amp;PerMode=PerGame&amp;Period=0&amp;PlayerID=2544&amp;PlusMinus=N&amp;Rank=N&amp;Season=2021-22&amp;SeasonSegment=&amp;SeasonType=Regular+Season&amp;ShotClockRange=&amp;Split=yoy&amp;VsConference=&amp;VsDivision=&#039; \
-H &#039;Connection: keep-alive&#039; \
-H &#039;sec-ch-ua: &amp;quot Not A;Brand&amp;quotv=&amp;quot99&amp;quot, &amp;quotChromium&amp;quotv=&amp;quot98&amp;quot, &amp;quotGoogle Chrome&amp;quotv=&amp;quot98&amp;quot&#039; \
-H &#039;Accept: application/json, text/plain, */*&#039; \
-H &#039;x-nba-stats-token: true&#039; \
-H &#039;sec-ch-ua-mobile: ?0&#039; \
-H &#039;User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36&#039; \
-H &#039;x-nba-stats-origin: stats&#039; \
-H &#039;sec-ch-ua-platform: &amp;quotLinux&amp;quot&#039; \
-H &#039;Origin: https://www.nba.com&#039; \
-H &#039;Sec-Fetch-Site: same-site&#039; \
-H &#039;Sec-Fetch-Mode: cors&#039; \
-H &#039;Sec-Fetch-Dest: empty&#039; \
-H &#039;Referer: https://www.nba.com/&#039; \
-H &#039;Accept-Language: en-US,en;q=0.9,fa;q=0.8&#039; \
-H &#039;If-Modified-Since: Sun, 13 Feb 2022 15:37:37 GMT&#039; \
--compressedدستوری که کپی کردید رو با فلگ &quot;--compressed&quot; توی ترمینال paste کنید و بعد از زدن دکمه enter و کمی صبر،‌ می‌بینیم که پاسخ‌های JSON برامون بر‌میگرده.البته اگه از مرورگر کروم استفاده می‌کنید دیگه لازم نیست خودتون فلگ --compressed رو اضافه کنید. چون دستور کپی شده از اونجا، خودش این فلگ رو داره.اگه به فلگ‌های &quot;-H&quot; توی دستور کپی شده دقت کنید، می‌بینید که کلی HTTP Header داریم که به خیلی از اون‌ها اصلا نیازی نداریم.می‌خوام که درخواستمون تا حد ممکن ساده باشه، پس تا جای ممکنه گزینه‌‌های اضافه رو حذف می‌کنم و چیزی که در آخر برامون می‌مونه، دستور زیره:curl &#039;https://stats.nba.com/stats/playerdashboardbyyearoveryear?DateFrom=&amp;DateTo=&amp;GameSegment=&amp;LastNGames=0&amp;LeagueID=00&amp;Location=&amp;MeasureType=Base&amp;Month=0&amp;OpponentTeamID=0&amp;Outcome=&amp;PORound=0&amp;PaceAdjust=N&amp;PerMode=PerGame&amp;Period=0&amp;PlayerID=2544&amp;PlusMinus=N&amp;Rank=N&amp;Season=2021-22&amp;SeasonSegment=&amp;SeasonType=Regular+Season&amp;ShotClockRange=&amp;Split=yoy&amp;VsConference=&amp;VsDivision=&#039; \
-H &#039;Connection: keep-alive&#039; \
-H &#039;x-nba-stats-token: true&#039; \
-H &#039;User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36&#039; \
-H &#039;Referer: https://www.nba.com/&#039; \
--compressedاز اونجایی که ریزالتی که تو ترمینال داریم مشاهده می‌کنیم، اصلا خوانا نیست، پس وقشته که از jq استفاده کنیم. برای این کار، آخر دستورمون &quot;| jq&quot; رو اضافه می‌کنیم. التبه jq بجز قشنگ و مرتب‌ کردن ریزالت، قابلیت‌های دیگه‌ای هم داره. براث مثال، با قرار دادن  &quot;| jq .resultSets[1].rowSet[0]&quot; آخر دستور، از jq می‌خوایم که برامون فقط سطر اول جدول رو برگردونه:curl &#039;https://stats.nba.com/stats/playerdashboardbyyearoveryear?DateFrom=&amp;DateTo=&amp;GameSegment=&amp;LastNGames=0&amp;LeagueID=00&amp;Location=&amp;MeasureType=Base&amp;Month=0&amp;OpponentTeamID=0&amp;Outcome=&amp;PORound=0&amp;PaceAdjust=N&amp;PerMode=PerGame&amp;Period=0&amp;PlayerID=2544&amp;PlusMinus=N&amp;Rank=N&amp;Season=2021-22&amp;SeasonSegment=&amp;SeasonType=Regular+Season&amp;ShotClockRange=&amp;Split=yoy&amp;VsConference=&amp;VsDivision=&#039; \
-H &#039;Connection: keep-alive&#039; \
-H &#039;x-nba-stats-token: true&#039; \
-H &#039;User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36&#039; \
-H &#039;Referer: https://www.nba.com/&#039; \
--compressed | jq &#039;.resultSets[1].rowSet[0]&#039;خروجی دستور بالا:[ &amp;quotBy Year&amp;quot, &amp;quot2021-22&amp;quot, 1610612747, &amp;quotLAL&amp;quot, &amp;quot2022-02-12T00:00:00&amp;quot,
40, 20, 20, 0.5, 36.7, 11, 21.1, 0.52, 2.8, 7.9, 0.352, 4.3, 5.7, 0.746,
1.1, 6.8, 7.9, 6.5, 3.2, 1.6, 1, 0.8, 2.3, 5.3, 29, -0.9, 52.7, 15, 4, 19,
19, 11, 18, 14, 2, 3, 7, 1, 1, 8, 17, 17, 9, 10, 6, 6, 15, 17, 10, 4, 7, 2,
15, 4, 18, 6, 17, 9, 264, &amp;quot2021-22&amp;quot ]نوشتن اسکریپت Node.js برای اسکرپ کردن چند صفحهتا اینجای کار URL و HTTP Header هایی که نیاز داریم رو پیدا کردیم و حالا می‌تونیم اسکریپتی بنویسم که API رو بطور اتوماتیک اسکرپ کنه. این اسکریپت رو می‌تونید تقریبا با هر زبانی که بخواید بنویسد، اما تو این مقاله قصد داریم تا با استفاده از Node.js و لایبری request این کارو کنیم.برای ساخت پروژه جاوااسکریپت جدید، به دایرکتوری مد نظرتون برید و تو ترمینال این ۲ دستور زیر رو وارد کنید:npm init -y
npm install --save request request-promise-nativeحالا باید درخواستمون رو از فرمت cURL به فرمت موردنیاز برای کتابخونه request، ترجمه کنیم. برای اینکه از مزایای async/await بهره‌مند هم بشیم و بتونیم کدهای asynchronous و خوانا بنویسیم، از Promise استفاده می‌کنیم. فایلی به اسم &quot;index.js&quot; ایجاد کنید و کدهای زیر رو توش قرار بدید:const rp = require(&amp;quotrequest-promise-native&amp;quot);
const fs = require(&amp;quotfs&amp;quot);

async function main() {
     console.log(&amp;quotMaking API Request...&amp;quot);

     // request the data from the JSON API
     const results = await rp({
     uri: &amp;quothttps://stats.nba.com/stats/playerdashboardbyyearoveryear?DateFrom=&amp;DateTo=&amp;GameSegment=&amp;LastNGames=0&amp;LeagueID=00&amp;Location=&amp;MeasureType=Base&amp;Month=0&amp;OpponentTeamID=0&amp;Outcome=&amp;PORound=0&amp;PaceAdjust=N&amp;PerMode=PerGame&amp;Period=0&amp;PlayerID=2544&amp;PlusMinus=N&amp;Rank=N&amp;Season=2021-22&amp;SeasonSegment=&amp;SeasonType=Regular+Season&amp;ShotClockRange=&amp;Split=yoy&amp;VsConference=&amp;VsDivision=&amp;quot,
     headers: {
          &amp;quotConnection&amp;quot: &amp;quotkeep-alive&amp;quot,
          &amp;quotUser-Agent&amp;quot: &amp;quotMozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36&amp;quot,
          &amp;quotx-nba-stats-origin&amp;quot: &amp;quotstats&amp;quot,
          &amp;quotReferer&amp;quot: &amp;quothttps://stats.nba.com/&amp;quot
     },
     json: true
});

     console.log(&amp;quotGot results =&amp;quot, results);

     // save the JSON to disk
     await fs.promises.writeFile&#40;&amp;quotoutput.json&amp;quot, JSON.stringify(results, null, 2&#41;);

     console.log(&amp;quotDone!&amp;quot);
}

// start the main script
main();و حالا در ترمینال با زدن دستور زیر، اسکریپت رو اجرا کنید:node index.jsو تمام! حالا اگه به فایل &quot;output.json&quot; که بعد از اجرای دستور بالا، توی دایرکتوری‌مون ایجاد شده برید،‌ داده‌های JSON رو مشاهده می‌کنید.گرفتن دیتای سایر بازیکن‌هاتا اینجا، کارمون تموم شده و اسکریپتی که نوشتیم دیتاهای مربوط به LeBron رو برامون اسکرپ می‌کنه. اما الان قصد دارم تا این یکم این اسکریپت رو ارتقاع بدم تا علاوه بر دیتای LeBron، دیتای چند بازیکن دیگه رو هم اسکرپ کنه.اگه به API URL دقت کنید، عبارت &quot;PlayerID&quot; رو مشاهده می‌کنید:الان فقط به ID سایر بازیکنان نیاز داریم. به صفحه چندتا دیگه از بازیکنا میرم و PlayerID اونارو برمیدارم. در مجموع ما الان ۴ تا بازیکن زیر رو داریم:Player ID | Player | URL2544 | LeBron James | https://stats.nba.com/player/2544/1629029 | Luka Doncic | https://stats.nba.com/player/1629029/201935 | James Harden | https://stats.nba.com/player/201935/202695 | Kawhi Leonard | https://stats.nba.com/player/202695/حالا فقط اسکریپتمون رو به شکل زیر تغییر میدیم تا بتونیم دیتای سایر بازیکن‌ها رو هم اسکرپ کنیم:const rp = require(&amp;quotrequest-promise-native&amp;quot);
const fs = require(&amp;quotfs&amp;quot);

// helper to delay execution by 300ms to 1100ms
async function delay() {
     const durationMs = Math.random() * 800 + 300;
     return new Promise(resolve =&gt; {
          setTimeout&#40;(&#41; =&gt; resolve(), durationMs);
     });
}

async function fetchPlayerYearOverYear(playerId) {
    console.log(`Making API Request for ${playerId}...`);

    // add the playerId to the URI and the Referer header
    // NOTE: we could also have used the `qs` option for the
    // query parameters.
    const results = await rp({
         uri: &amp;quothttps://stats.nba.com/stats/playerdashboardbyyearoveryear?DateFrom=&amp;DateTo=&amp;GameSegment=&amp;LastNGames=0&amp;LeagueID=00&amp;Location=&amp;MeasureType=Base&amp;Month=0&amp;OpponentTeamID=0&amp;Outcome=&amp;PORound=0&amp;PaceAdjust=N&amp;PerMode=PerGame&amp;Period=0&amp;&amp;quot +
`PlayerID=${playerId}` +
&amp;quot&amp;PlusMinus=N&amp;Rank=N&amp;Season=2019-20&amp;SeasonSegment=&amp;SeasonType=Regular+Season&amp;ShotClockRange=&amp;Split=yoy&amp;VsConference=&amp;VsDivision=&amp;quot,
          headers: {
              &amp;quotConnection&amp;quot: &amp;quotkeep-alive&amp;quot,
              &amp;quotUser-Agent&amp;quot: &amp;quotMozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36&amp;quot,
              &amp;quotx-nba-stats-origin&amp;quot: &amp;quotstats&amp;quot,
              &amp;quotReferer&amp;quot: `https://stats.nba.com/player/${playerId}/`
          },
          json: true
     });

     // save to disk with playerID as the file name
     await fs.promises.writeFile&#40;
         `${playerId}.json`,
         JSON.stringify(results, null, 2&#41;
     );
}

async function main() {
     // PlayerIDs for LeBron, Harden, Kawhi, Luka
     const playerIds = [2544, 201935, 202695, 1629029];
     console.log(&amp;quotStarting script for players&amp;quot, playerIds);

    // make an API request for each player
     for (const playerId of playerIds) {
          await fetchPlayerYearOverYear(playerId);

          // be polite to our friendly data hosts and
        // don&#039;t crash their servers
        await delay();
     }

     console.log(&amp;quotDone!&amp;quot);
}

// start the main script
main();در کد بالا، من فانکشنی async به اسم fetchPlayerYearOverYear نوشتم و با استفاده از حلقه for، تمام ID های موجود در آرایه رو fetch کردم و از اونجایی هم که نمی‌خوام سرور رو با تعداد درخواست‌های بالام بمب بارون کنم، بعد از هر مرحله fetch، از delay استفاده کردم.اگه تعداد درخواست‌هامون در یک مدت زمانی کوتاه زیاد باشه، ممکنه به عنوان اسپمر شناخته بشیم و بلاک شویم.اولین بخش از مقاله یعنی آموزش اسکرپ کردن به روش JSON API با استفاده از Node.js تموم شد و حالا روش ۲ و ۳ مونده.روش شماره ۲: Server-side Rendered HTMLعلاوه بر روش قبلی که گرفتن دیتا به‌صورت async از طریق API بود، روش دیگه‌ای وجود داره که در اون دیتا به‌صورت مستقیم و قبل از اینکه صفحه وب لود بشه به HTML رندر میشه. روشی که وب سرورها استفاده می‌کنن. در این قسمت توضیح میدم که چطوری می‌تونیم با دانلود و parse کردن (تبدیل) HTML به کمک Cheerio، دیتا استخراج کنیم.پیدا کردن HTML همراه با دیتادر ایجا قراره داده‌هارو از box scores وب‌سایت espn.com استخراج کنیم.مرحله ۱: مطلع شدن از داینامیک بودن داده‌هامثل روش اول، در این روش هم باید مطمئن بشیم که داده‌ها به‌صورت داینامیک در صفحه لود میشن. برای مثال، می‌خوایم دیتاهای box scores مربوط به یکی از بازی‌های بسکتبال 76ers رو اسکرپ کنیم.لینک گزارش بازی:  https://www.espn.com/nba/boxscore?gameId=401160888.اسکرین شات از Box Score مربوط به بازیدقیقا همون کاری که در روش ۱ برای دیدن Source صفحه کردیم، اینبار هم انجام میدیم. روی صفحه راست کلیک کنید و گزینه View Page Source رو بزنید و یا از میانبرهای Ctrl+U استفاده کنید.حالا تو صفحه سورس Ctrl+F بزنید و یکی از نتایجی که تو جدول مشاهده می‌کنید رو سرچ کنید. برای مثال من &quot;0-11&quot; رو سرچ می‌کنم.اسکرین شات از source صفحهاینم از چیزی که دنبالش بودیم!مرحله ۲: پیدا کردن نحوه دسترسی به دیتابعد از اینکه از وجود داده‌ها در HTML مطلع شدیم، حالا باید ببینیم که چطوری می‌تونیم به اون‌ها از طریق مرورگر دسترسی پیدا کنیم. تو اینجا ما به یه چیزی نیاز داریم تا بخش‌هایی از HTML که برامون اهمیت دارن رو شناسایی کنه، چیزی که CSS Selector ها هم به‌خوبی بتونن باهاش کار کنن.دوباره به صفحه بر می‌گردیم و روی جدول راست کلیک می‌کنیم و گزینه Inspect رو میزنیم. در نتجیه باید یه چی شبیه به چیزی که در تصویر زیر می‌بینید رو مشاهده کنید:اسکرین شات از HTML مربوط به جدولالان به یک Selector نیاز داریم که تمام سطرهای جدول یا همون &lt;tr&gt; هارو برامون برگردونه که این کار رو با استفاده از فانکشن &quot;document.querySelectorAll()&quot; انجام میدیم. برای این کار، توی Console مرورگرتون کد زیر رو بزنید و enter کنید:document.querySelectorAll(&#039;tr&#039;)خروجی کد بالا:&gt; NodeList(41) [tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr.highlight, tr.highlight, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr.highlight, tr.highlight, tr, tr, tr.highlight, tr.highlight, tr, tr]همونطور که می‌بینید استفاده از &quot;tr&quot; خیلی کلی هست و کدی که نوشتیم برامون ۴۱ سطر برگردوند و این در حالی است که تیم 76ers فقط ۱۳ بازیکن در جدول داره. دوباره به کدای HTML در Inspector نگاه کنید و متوجه میشید که المنت‌های والد تگ‌های &lt;tr&gt;، یکسری ID و Class دارن:کدهای HTMl جدول Box Scoreبا استفاده از این ID و Class ها، کد Selector مون رو بازنویسی می‌کنیم:document.querySelectorAll(&#039;.gamepackage-away-wrap tbody tr&#039;)خروجی کد بالا:&gt; NodeList(15) [tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr.highlight, tr.highlight]به هدفمون خیلی نزدیک شدیم. ولی اگه به خروجی نگاه کنید می‌بینید که دو تگ &lt;tr&gt; با کلاس highlight برامون برگردونده که خلاصه کل سطرهاست و بهشون نیازی نداریم. می‌تونیم خیلی راحت دستور Selector رو آپدیت کنیم تا اونارو فیلتر کنه:document.querySelectorAll(&#039;.gamepackage-away-wrap tbody tr:not(.highlight)&#039;)خروجی کد بالا:&gt; NodeList(13) [tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr, tr]عالی شد! حالا یک Selector داریم که تمام سطرهای بازیکن‌ها رو از جدول صفحه Box Score برامون برمیگردونه. کار این قسمت تموم و میشه و حالا نوبت نوشتن اسکریپته.نوشتن اسکریپت Node.js برای اسکرپ کردن صفحهالان علاوه بر URL صفحه‌ای که میخوایم اسکرپ کنیم، یک Selector هم داریم که برامون تمام سطرهایی که از جدول صفحه Box Score می‌خوایم رو برمیگردونه.دانلود صفحه HTMLکاری که اینجا می‌خوایم بکنیم، دقیقا مثل کاریه که در روش قبلی انجام دادیم. به دایرکتوری که میخواید پروژتون رو اونجا ایجاد کنید برید و دستورات زیر رو تو ترمینال وارد کنید:npm init -y
npm install --save request request-promise-nativeمن معمولا ترجیح میدم که فایل HTML رو تو دایرکتوری ذخیره کنم تا وقتی که دارم فرآیند Parse کردن رو تکرار می‌کنم، مدام به سرور درخواست نفرستم. پس اول بریم یک اسکریپت به اسم &quot;index.js&quot; بسازیم تا صفحه HTML رو برامون دانلود کنه:const rp = require(&#039;request-promise-native&#039;);
const fs = require(&#039;fs&#039;);

async function downloadBoxScoreHtml() {
     // where to download the HTML from
     const uri = &#039;https://www.espn.com/nba/boxscore?gameId=401160888&#039;;
     // the output filename
     const filename = &#039;boxscore.html&#039;;

     // download the HTML from the web server
     console.log(`Downloading HTML from ${uri}...`);
     const results = await rp({ uri: uri });

     // save the HTML to disk
     await fs.promises.writeFile&#40;filename, results&#41;;
}

async function main() {
     console.log(&#039;Starting...&#039;);

     await downloadBoxScoreHtml();

     console.log(&#039;Done!&#039;);
}

main();حالا با وارد کردن دستور زیر در ترمینال، اسکریپت رو ران می‌کنیم:node index.jsحالا در دایرکتوری پروژه، فایلی به اسم boxscore.html داریم که حاوی HTML صفحه‌ی مد نظرمونه.قبل از Parse کردن HTML، بیاید اسکریپت رو آپدیت کنیم تا فقط وقتی فایل رو دانلود که اون فایل در دایرکتوری وجود نداشته باشه. برای انجام این کار، تکه کد زیر (مثل تصویر پایین‌تر) رو قبل از قسمت ایجاد درخواست اضافه می‌کنیم:// check if we already have the file
const fileExists = fs.existsSync(filename);
if (fileExists) {
     console.log(`Skipping download for ${uri} since ${filename} already exists.`);
     return;
}تبدیل (Parse) کردن HTML با استفاده از Cheerioبهترین راه برای بیرون کشیدن دیتا از HTML، استفاده از یک HTML Parser مثل Cheerio می‌باشد. Cheerio شباهت زیادی با jQuery داره، با این تفاوت که سمت سروره. ابتدا با زدن دستور زیر در ترمینال، Cheerio رو نصب می‌کنیم:npm install --save cheerioبرای شروع و استفاده از Cheerio، باید HTML رو به عنوان رشته (String) بهش پاس بدیم تا اونو برامون Parse و به قول معروف queryable یا قابل پرس و جو کنه و این کارو با استفاده از دستور load انجام میدیم:const $ = cheerio.load(&#039;&lt;html&gt;...&lt;/html&gt;&#039;)کار خوندن و load کردن فایل HTML توسط Cheerio رو با دستورات زیر انجام میدیم:// the input filename
const htmlFilename = &#039;boxscore.html&#039;;
// read the HTML from disk
const html = await fs.promises.readFile&#40;htmlFilename&#41;;
// parse the HTML with Cheerio
const $ = cheerio.load(html);بعد از اینه HTML پارس شد، می‌تونیم با استفاده از فانکشن $، Selector مون رو بنویسم:const $trs = $(&#039;.gamepackage-away-wrap tbody tr:not(.highlight)&#039;)کدی که نوشتیم، تمام ندهای tr جدول رو برامون برمیگردونه و برای اینکه از این بابت هم مطمئن شیم، می‌تونیم از &quot;$.html&quot; استفاده کنیم:console.log($.html($trs));خروجی کد بالا:&lt;tr&gt;
     &lt;td class=&amp;quotname&amp;quot&gt;
          &lt;a name=&amp;quot&amp;lpos=nba:game:boxscore:playercard&amp;quot href=&amp;quothttps://www.espn.com/nba/player/_/id/6440/tobias-harris&amp;quot data-player-uid=&amp;quots:40~l:46~a:6440&amp;quot&gt;
               &lt;span&gt;T. Harris&lt;/span&gt;
               &lt;span class=&amp;quotabbr&amp;quot&gt;T. Harris&lt;/span&gt;
          &lt;/a&gt;
     &lt;span class=&amp;quotposition&amp;quot&gt;SF&lt;/span&gt;
     &lt;/td&gt;
     &lt;td class=&amp;quotmin&amp;quot&gt;38&lt;/td&gt;
     &lt;td class=&amp;quotfg&amp;quot&gt;7-17&lt;/td&gt;
      &lt;td class=&amp;quot3pt&amp;quot&gt;3-7&lt;/td&gt;
     &lt;td class=&amp;quotft&amp;quot&gt;1-1&lt;/td&gt;
     &lt;td class=&amp;quotoreb&amp;quot&gt;0&lt;/td&gt;
     &lt;td class=&amp;quotdreb&amp;quot&gt;5&lt;/td&gt;
     &lt;td class=&amp;quotreb&amp;quot&gt;5&lt;/td&gt;
     &lt;td class=&amp;quotast&amp;quot&gt;2&lt;/td&gt;
     &lt;td class=&amp;quotast&amp;quot&gt;1&lt;/td&gt;
     &lt;td class=&amp;quotast&amp;quot&gt;0&lt;/td&gt;
     &lt;td class=&amp;quotast&amp;quot&gt;1&lt;/td&gt;
     &lt;td class=&amp;quotast&amp;quot&gt;2&lt;/td&gt;
     &lt;td class=&amp;quotast&amp;quot&gt;3&lt;/td&gt;
     &lt;td class=&amp;quotplusminus&amp;quot&gt;-10&lt;/td&gt;
     &lt;td class=&amp;quotpts&amp;quot&gt;18&lt;/td&gt;
&lt;/tr&gt;
...حالا می‌تونیم یک object mapping برای اتریبیوت class مربوط به &lt;td&gt; بسازیم تا مقادیر اونو ذخیره کنیم. این کارو روی هر سطر تکرار می‌کنیم تا تمام داده‌ها رو بیرون بکشیم:const values = $trs.toArray().map(tr =&gt; {
     // find all children &lt;td&gt;
     const tds = $(tr).find(&#039;td&#039;).toArray();

     // create a player object based on the &lt;td&gt; values
     const player = {};
     for (td of tds) {
          // parse the &lt;td&gt;
          const $td = $(td);

          // map the td class attr to its value
          const key = $td.attr(&#039;class&#039;);
          const value = $td.text();
          player[key] = value;
     }

     return player:
});حالا اگه یه نگاهی به خروجی کارمون بندازیم، یه چیزی شبیه به مقادیر زیر رو خواهیم داشت:[
     { 
          &amp;quotname&amp;quot: &amp;quotT. HarrisT. HarrisSF&amp;quot,
          &amp;quotmin&amp;quot: &amp;quot38&amp;quot,
          &amp;quotfg&amp;quot: &amp;quot7-17&amp;quot,
          &amp;quot3pt&amp;quot: &amp;quot3-7&amp;quot, 
          &amp;quotft&amp;quot: &amp;quot1-1&amp;quot,
          &amp;quotoreb&amp;quot: &amp;quot0&amp;quot, 
          &amp;quotdreb&amp;quot: &amp;quot5&amp;quot,
          &amp;quotreb&amp;quot: &amp;quot5&amp;quot, 
          &amp;quotast&amp;quot: &amp;quot2&amp;quot, 
          &amp;quotstl&amp;quot: &amp;quot1&amp;quot,
          &amp;quotblk&amp;quot: &amp;quot0&amp;quot,
          &amp;quotto&amp;quot: &amp;quot2&amp;quot,
          &amp;quotpf&amp;quot: &amp;quot3&amp;quot,
          &amp;quotplusminus&amp;quot: &amp;quot-10&amp;quot, 
          &amp;quotpts&amp;quot: &amp;quot18&amp;quot
     }
     ...
]به اون چیزی که می‌خوایم خیلی نزدیک شدیم، ولی چندتا مشکل کوچیک داریم. به‌نظر میرسه مقدار name اشتباهه و تمام اعداد، به‌صورت String ذخیره شده. این مشکل رو از روش‌های مختلفی میشه حل کرد و ما از سریع‌ترین روش‌های ممکن برای حلش استفاده می‌کنیم.اول میریم سراغ درست کردن name. اگه به کد HTML نگاه کنید، متوجه میشید که ستون اول نسبت به سایر ستون‌ها، کمی متفاوته:&lt;td class=&amp;quotname&amp;quot&gt;
     &lt;a name=&amp;quot&amp;lpos=nba:game:boxscore:playercard&amp;quot href=&amp;quothttps://www.espn.com/nba/player/_/id/6440/tobias-harris&amp;quot data-player-uid=&amp;quots:40~l:46~a:6440&amp;quot&gt;
          &lt;span&gt;T. Harris&lt;/span&gt;
          &lt;span class=&amp;quotabbr&amp;quot&gt;T. Harris&lt;/span&gt;
     &lt;/a&gt;
     &lt;span class=&amp;quotposition&amp;quot&gt;SF&lt;/span&gt;
&lt;/td&gt;به شکل زیر، اولین تگ &lt;span&gt; توی تگ &lt;a&gt; مربوط به ستون name رو انتخاب می‌کنیم:for (td of tds) {
     const $td = $(td);

     // map the td class attr to its value
    const key = $td.attr(&#039;class&#039;);

     let value;
     if (key === &#039;name&#039;) {
          value = $td.find(&#039;a span:first-child&#039;).text();
     } else {
          value = $td.text();
     }

     player[key] = value;
}و حالا نوبت درست کردن اعداده. از اونجایی که می‌دونیم تمام مقادیرمون String هستن، خیلی ساده می‌تونیم تمام اعداد رو به شکل زیر، تبدیل به عدد کنیم:player[key] = isNaN(+value) ? value : +value;با تغییراتی که انجام دادیم، خروجی‌مون بدین شکل میشه:[ 
     {
          &amp;quotname&amp;quot: &amp;quotT. Harris&amp;quot,
          &amp;quotmin&amp;quot: 38,
          &amp;quotfg&amp;quot: &amp;quot7-17&amp;quot,
          &amp;quot3pt&amp;quot: &amp;quot3-7&amp;quot,
          &amp;quotft&amp;quot: &amp;quot1-1&amp;quot,
          &amp;quotoreb&amp;quot: 0,
          &amp;quotdreb&amp;quot: 5,
          &amp;quotreb&amp;quot: 5,
          &amp;quotast&amp;quot: 2,
          &amp;quotstl&amp;quot: 1,
          &amp;quotblk&amp;quot: 0,
          &amp;quotto&amp;quot: 2,
          &amp;quotpf&amp;quot: 3,
          &amp;quotplusminus&amp;quot: -10,
          &amp;quotpts&amp;quot: 18
     },
     ...
]حالا تمام کاری که مونده انجام بدیم، ذخیره کردن مقادیر توی دایرکتوریمونه:// save the scraped results to disk
await fs.promises.writeFile&#40;
     &#039;boxscore.json&#039;,
     JSON.stringify(values, null, 2&#41;
);و تمام! ما HTML رو که داده‌ها از قبل توش رندر شده بود رو دانلود کردیم و بعد با استفاده از Cheerio اونارو لود و Parse کردیم و در نهایت دیتا رو با فرمت JSON در فایل مربوط، ذخیره کردیم.کد کامل اسکریپتی که نوشتیم:const rp = require(&#039;request-promise-native&#039;);
const fs = require(&#039;fs&#039;);
const cheerio = require(&#039;cheerio&#039;);

async function downloadBoxScoreHtml() {
     // where to download the HTML from
     const uri = &#039;https://www.espn.com/nba/boxscore?gameId=401160888&#039;;
     // the output filename
     const filename = &#039;boxscore.html&#039;;

     // check if we already have the file
     const fileExists = fs.existsSync(filename);
     if (fileExists) {
          console.log(`Skipping download for ${uri} since ${filename} already exists.`);
          return;
     }

     // download the HTML from the web server
     console.log(`Downloading HTML from ${uri}...`);
     const results = await rp({ uri: uri });

     // save the HTML to disk
     await fs.promises.writeFile&#40;filename, results&#41;;
}

async function parseBoxScore() {
     console.log(&#039;Parsing box score HTML...&#039;);

     // the input filename
     const htmlFilename = &#039;boxscore.html&#039;;
     // read the HTML from disk
     const html = await fs.promises.readFile&#40;htmlFilename&#41;;
     // parse the HTML with Cheerio
     const $ = cheerio.load(html);

     // Get our rows
     const $trs = $(&#039;.gamepackage-away-wrap tbody tr:not(.highlight)&#039;);

     const values = $trs.toArray().map(tr =&gt; {
          // find all children &lt;td&gt;
          const tds = $(tr).find(&#039;td&#039;).toArray();

          // create a player object based on the &lt;td&gt; values
          const player = {};
          for (td of tds) {
               const $td = $(td);

               // map the td class attr to its value
               const key = $td.attr(&#039;class&#039;);
               let value;
               if (key === &#039;name&#039;) {
                    value = $td.find(&#039;a span:first-child&#039;).text();
               } else {
                    value = $td.text();
               }
              player[key] = isNaN(+value) ? value : +value;
          }

          return player;
         });

         return values;
     }

async function main() {
     console.log(&#039;Starting...&#039;);

     await downloadBoxScoreHtml();
     const boxScore = await parseBoxScore();

     // save the scraped results to disk
     await fs.promises.writeFile&#40;
         &#039;boxscore.json&#039;,
         JSON.stringify(boxScore, null, 2&#41;
     );

     console.log(&#039;Done!&#039;);
}

main():اینم از آموزش اسکرپ کردن به روش Server-side Rendered HTML. روش‌‌های ۱ و ۲ تموم شد و حالا فقط روش شماره ۳ مونده.روش شماره ۳: JavaScript rendered HTMLدر این قسمت قراره روش اسکرپ کردن HTML بعد از اجرا شدن JavaScript در صفحه رو توضیح بدم. در بسیاری از موارد می‌تونیم این کارو به شکلی که در روش ۱ انجام دادیم، انجام بدیم، اما معمولا گرفتن HTML و کار روش، ممکنه راحت‌تر باشه. اینجا دیگه کتابخونه Request که قبلا ازش استفاده کردیم، به کارمون نمیاد. چون، این کتابخونه فقط HTML رو دانلود می‌کنه و هیچ جاوااسکریپتی رو در صفحه اجرا نمی‌کنه. پس نیازه که از یه کتابخونه دیگه‌ای استفاده کنیم و Puppeteer همون کتابخونه قدرتمندیه که قراره در این قسمت ازش استفاده کنیم.برای این قسمت، قراره دیتای مربوط به پرتاب‌های Steph Curry از وب‌سایت Basketball Reference رو اسکرپ کنیم.نوشتن اسکریپت Node.js برای اسکرپ کردن صفحه بعد از اجرا شدن جاوااسکریپتهدف‌مون اسکرپ کردن دیتا از چارت مربوط پرتاب‌های بازی Steph Curry موجود در صفحه زیره:https://www.basketball-reference.com/players/c/curryst01/shooting/2020 Steph Curry نمودار پرتاب‌های بازی با راست کلیک روی یکی از نمادهای ● یا × روی عکس و زدن گزینه Inspect (مثل کاری که در روش‌های ۱ و ۲ انجام دادیم)، می‌تونیم تگ‌های تو در تو &lt;div&gt; رو مشاهده کنیم:کدهای HTML مربوط به چارتحالا با استفاده از Console مرورگر، یک Selector می‌نویسیم تا تگ‌های &lt;div&gt; که میخوایم رو برامون بگیره:document.querySelectorAll(&#039;.shot-area &gt; div&#039;)خروجی کد بالا:&gt; NodeList(66) [div.tooltip.miss, div.tooltip.miss, div.tooltip.miss, div.tooltip.make, div.tooltip.make, ...]حالا با استفاده از این Selector میریم تا اسکریپت Node.js رو بنویسیم.راه‌اندازی Puppeteerبرای ساخت پروژه جاوااسکریپت جدید، به دایرکتوری مد نظرتون برید، یک فایل به اسم &quot;index.js&quot; بسازید و تو ترمینال این ۲ دستور زیر رو وارد کنید:npm init -y
npm install --save cheerio puppeteerکار با لایبری Puppeteer، کمی از request پیچیده‌تره، ولی سعی می‌کنم تا به ساده‌ترین شکل ممکن کار باهاشو بهتون توضیح بدم. Puppeteer با اجرای یک مرورگر headless کروم کار می‌کنه، بنابراین به‌صورت تئوری هر کاری که مرورگر شما بتونه انجام بده، اینم میتونه انجامش بده. برای شروع، نیاز داریم تا ابتدا puppeteer رو import کنیم و سپس مرورگر رو اجرا کنیم:const fs = require(&#039;fs&#039;);
const puppeteer = require(&#039;puppeteer&#039;);

async function main() {
     console.log(&#039;Starting...&#039;);

     const browser = await puppeteer.launch();

     // TODO download the HTML after running js on the page

     await browser.close();

     console.log(&#039;Done!&#039;);
}

main();در قدم بعدی، نیاز که یک صفحه جدید بسازیم تا HTML رو باش fetch کنیم. برای ساخت صفحه، دوست دارم که یه helper function (تابع کمکی) داشته باشم تا بتونم تمام گزینه‌هایی که برای درخواستم می‌خوام رو، توش قرار بدم. تو اینجا من timeout رو ۲۰ ثانیه قرار دادم و از یک User Agent string جعلی هم استفاده کردم:async function newPage(browser) {
     // get a new page
     page = await browser.newPage();
     page.setDefaultTimeout(20000); // 20s

     // spoof user agent
     await page.setUserAgent(&#039;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36&#039;);

     // pretend to be desktop
     await page.setViewport({
          width: 1980,
          height: 1080,
     });

     return page;
}پس از ست کردن صفحه، حالا باید کار fetch کردن و اجرای JS رو انجام بدیم که دوباره این کار رو با یک helper function دیگه انجام دادم:async function fetchUrl(browser, url) {
     const page = await newPage(browser);

     await page.goto(url, {
          timeout: 20000,
          waitUntil: &#039;domcontentloaded&#039;
     });

     const html = await page.content();
     await page.close();

     return html;
}دانلود صفحه HTMLبا دو فانکشنی که نوشتیم، می‌تونیم کدی بنویسیم تا دیتای مربوط به پرتاب‌ها رو برامون دانلود کنه:async function downloadShootingData(browser) {
     const url = &#039;https://www.basketball-reference.com/players/c/curryst01/shooting/2020&#039;;
     const htmlFilename = &#039;shots.html&#039;;

     // download the HTML from the web server
     console.log(`Downloading HTML from ${url}...`);
     const html = await fetchUrl(browser, url);

     // save the HTML to disk
     await fs.promises.writeFile&#40;htmlFilename, html&#41;;
}دوباره، از اونجای puppeteer ممکنه کمی کند باشه، توصیه می‌کنم که قبل از اجرای فانکشن fetchUrl، چک کنید که آیا فایل رو از قبل داریم یا نه:async function downloadShootingData(browser) {
     const url = &#039;https://www.basketball-reference.com/players/c/curryst01/shooting/2020&#039;;
     const htmlFilename = &#039;shots.html&#039;;

     // check if we already have the file
     const fileExists = fs.existsSync(htmlFilename);
     if (fileExists) {
          console.log(`Skipping download for ${url} since ${htmlFilename} already exists.`);
          return;
     }

     // download the HTML from the web server
     console.log(`Downloading HTML from ${url}...`);
     const html = await fetchUrl(browser, url);

     // save the HTML to disk
     await fs.promises.writeFile&#40;htmlFilename, html&#41;;
}حالا فقط نیازه تا قبل از اینکه مرورگر رو اجرا کنیم، فانکشن رو صدا بزنیم.async function main() {
     console.log(&#039;Starting...&#039;);

     // download the HTML after javascript has run
     const browser = await puppeteer.launch();
     await downloadShootingData(browser);
     await browser.close();

     console.log(&#039;Done!&#039;);
}سپس با دستور زیر در ترمینال، اسکریپت رو اجرا می‌کنیم:node index.jsحالا اگه فایل shots.html رو چک کنید، تمام تگ‌های &lt;div&gt; که می‌خوایم رو توش می‌بینید که منتظرن تا اونارو Parse و سپس ذخیرشون کنیم.تبدیل (Parse) کردن HTML با استفاده از Cheerioفرآيند Parse کردن در این روش، به همون شکلی که در روش ۲ انجام دادیم، انجام میشه. یک فایل HTML داریم و می‌خوایم داده رو با استفاده از selector ازش استخراج کنیم. دوباره به تگ &lt;div&gt; مربوط به shot نگاه کنید. به‌نظرتون چه داده‌هایی می‌تونیم ازش بدست بیاریم؟نظرتون چیه که موقیت x و y رو از اتریبیوت style، مقدار امتیاز پرتاب رو از اتریبیوت tip و اینکه پرتاب موفقیت آمیز بود یا خیر رو از اتریبیوت class بگیریم؟پس بریم فایل HTML رو لود و سپس با استفاده از cheerio اونو Parse کنیم:async function parseShots() {
     console.log(&#039;Parsing shots HTML...&#039;);

     // the input filename
     const htmlFilename = &#039;shots.html&#039;;
     // read the HTML from disk
     const html = await fs.promises.readFile&#40;htmlFilename&#41;;
     // parse the HTML with Cheerio
     const $ = cheerio.load(html);

     // for each of the shot divs, convert to JSON
     const divs = $(&#039;.shot-area &gt; div&#039;).toArray();

     // TODO: convert divs to shot JSON objects

     return shots;
}الان تگ‌های &lt;div&gt; رو به عنوان آرایه در اختیار داریم. حالا با map کردن اونا به آبجکت‌های JSON، مقادیر اتربیوبت‌هایی که در بالا بهشون اشاره کردیم رو برمی‌گردونیم:const shots = divs.map(div =&gt; {
      const $div = $(div);

     // style=&amp;quotleft:50px;top:120px&amp;quot -&gt; x = 50, y = 120
     // slice -2 to drop &amp;quotpx&amp;quot, prefix with `+` to make a number
     const x = +$div.css(&#039;left&#039;).slice(0, -2);
     const y = +$div.css(&#039;top&#039;).slice(0, -2);

     // class=&amp;quottooltip make&amp;quot or &amp;quottooltip miss&amp;quot
     const madeShot = $div.hasClass(&#039;make&#039;);

     // tip=&amp;quot...Made 3-pointer...&amp;quot
     const shotPts = $div.attr(&#039;tip&#039;).includes(&#039;3-pointer&#039;) ? 3 : 2;

     return { x, y, madeShot, shotPts };
});کد بالا باید خروجی شبیه به زیر رو بهمون بده:[
     {
          &amp;quotx&amp;quot: 259,
          &amp;quoty&amp;quot: 331,
          &amp;quotmadeShot&amp;quot: false,
          &amp;quotshotPts&amp;quot: 3
          },
          ...
]حالا فقط باید فانکشن parseShots() رو به فانکشن main() وصل کنیم و نتیحه رو ذخیره کنیم:async function main() {
     console.log(&#039;Starting...&#039;);

     // download the HTML after javascript has run
     const browser = await puppeteer.launch();
     await downloadShootingData(browser);
     await browser.close();

     // parse the HTML
     const shots = await parseShots();

     // save the scraped results to disk
     await fs.promises.writeFile&#40;&#039;shots.json&#039;, JSON.stringify(shots, null, 2&#41;);

     console.log(&#039;Done!&#039;);
}و الان اگه فایل shots.json رو چک کنید، باید نتیجه کارمون رو مشاهده کنید.کد کامل اسکریپتی که نوشتیم:const fs = require(&#039;fs&#039;);
const cheerio = require(&#039;cheerio&#039;);
const puppeteer = require(&#039;puppeteer&#039;);

const TIMEOUT = 20000; // 20s timeout with puppeteer operations
const USER_AGENT =
  &#039;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36&#039;;

async function newPage(browser) {
  // get a new page
  page = await browser.newPage();
  page.setDefaultTimeout(TIMEOUT);

  // spoof user agent
  await page.setUserAgent(USER_AGENT);

  // pretend to be desktop
  await page.setViewport({
    width: 1980,
    height: 1080,
  });

  return page;
}

async function fetchUrl(browser, url) {
  const page = await newPage(browser);

  await page.goto(url, { timeout: TIMEOUT, waitUntil: &#039;domcontentloaded&#039; });
  const html = await page.content(); // sometimes this seems to hang, so now we create a new page each time
  await page.close();

  return html;
}

async function downloadShootingData(browser) {
  const url =
    &#039;https://www.basketball-reference.com/players/c/curryst01/shooting/2020&#039;;
  const htmlFilename = &#039;shots.html&#039;;

  // check if we already have the file
  const fileExists = fs.existsSync(htmlFilename);
  if (fileExists) {
    console.log(
      `Skipping download for ${url} since ${htmlFilename} already exists.`
    );
    return;
  }

  // download the HTML from the web server
  console.log(`Downloading HTML from ${url}...`);
  const html = await fetchUrl(browser, url);

  // save the HTML to disk
  await fs.promises.writeFile&#40;htmlFilename, html&#41;;
}

async function parseShots() {
  console.log(&#039;Parsing box score HTML...&#039;);

  // the input filename
  const htmlFilename = &#039;shots.html&#039;;
  // read the HTML from disk
  const html = await fs.promises.readFile&#40;htmlFilename&#41;;
  // parse the HTML with Cheerio
  const $ = cheerio.load(html);

  // for each of the shot divs, convert to JSON
  const divs = $(&#039;.shot-area &gt; div&#039;).toArray();
  const shots = divs.map(div =&gt; {
    const $div = $(div);

    // style=&amp;quotleft:50px;top:120px&amp;quot -&gt; x = 50, y = 120
    const x = +$div.css(&#039;left&#039;).slice(0, -2);
    const y = +$div.css(&#039;top&#039;).slice(0, -2);

    // class=&amp;quottooltip make&amp;quot or &amp;quottooltip miss&amp;quot
    const madeShot = $div.hasClass(&#039;make&#039;);

    // tip=&amp;quot...Made 3-pointer...&amp;quot
    const shotPts = $div.attr(&#039;tip&#039;).includes(&#039;3-pointer&#039;) ? 3 : 2;

    return {
      x,
      y,
      madeShot,
      shotPts,
    };
  });

  return shots;
}

async function main() {
  console.log(&#039;Starting...&#039;);

  // download the HTML after javascript has run
  const browser = await puppeteer.launch();
  await downloadShootingData(browser);
  await browser.close();

  // parse the HTML
  const shots = await parseShots();

  // save the scraped results to disk
  await fs.promises.writeFile&#40;&#039;shots.json&#039;, JSON.stringify(shots, null, 2&#41;);

  console.log(&#039;Done!&#039;);
}

main();اینم از روش شماره ۳ و آخرین روش! موفق شدیم تا یک صفحه رو پس اجرا شدن JavaScript روش دانلود، کدهای HTML اش رو Parse و داده‌ها رو ازش استخراج کنیم و در نهایت در یک فایل JSON ذخیره کنیم.امیدوارم که این مقاله براتون مفید بوده باشه و تونسته باشم که به خوبی وب اسکرپینگ با جاوا اسکریپت رو توضیح داده باشم.فقط حواستون باشه که با اسکرپ کردن میشه کارهای زیادی انجام داد و اگه قراره ازش استفاده کنید، در جای درست و به روش درست ازش استفاده کنید (از اسکرپ کردن خیلی راحت میشه سو استفاده ها کرد :) ) .خلاصه اگه سوالی، پیشنهادی چیزی داشتید تو کامنت برام بنویسید. همچنین، من تمام کدهایی که نوشتیم + خروجی اون‌هارو تو ریپازیتوری گیت‌هابم هم قرار دادم که می‌تونید ازشون استفاده کنید.- منبع</description>
                <category>Xeniac</category>
                <author>یوسف روشندل</author>
                <pubDate>Thu, 24 Feb 2022 16:51:18 +0330</pubDate>
            </item>
                    <item>
                <title>پلاگینی برای سنجش قدرت رمزعبور کاربر در فلاتر</title>
                <link>https://virgool.io/Xeniac/%D9%BE%D9%84%D8%A7%DA%AF%DB%8C%D9%86%DB%8C-%D8%A8%D8%B1%D8%A7%DB%8C-%D8%B3%D9%86%D8%AC%D8%B4-%D9%82%D8%AF%D8%B1%D8%AA-%D8%B1%D9%85%D8%B2%D8%B9%D8%A8%D9%88%D8%B1-%DA%A9%D8%A7%D8%B1%D8%A8%D8%B1-%D8%AF%D8%B1-%D9%81%D9%84%D8%A7%D8%AA%D8%B1-ryvrgczyeon6</link>
                <description>چی میشه اگر بخوایم که داخل اپلیکیشن فلاتری خودمون رمزعبور وارد شده توسط کاربر رو چک کنیم و ببینیم که آیا به اندازه‌ی کافی قویه یا نه؟ امروز راه‌حل این مورد آسون‌تر از همیشه اینجاست.Flutter Password Validatorحتما درست مثل من برای شما هم پیش اومده که درحال کاربر بر روی اپلیکیشنی بودید که داخل اون یک قسمت ثبت‌نام برای کاربران داره و طبیعتا این صفحه شامل قسمتی که کاربر باید رمزعبور دلخواهش رو وارد میکرده. امروزه همه‌ی کاربران حوزه اینترنت این رو میدونن که اولین مرحله برای امن‌سازی یک حساب کاربری انتخاب یک رمزعبور قوی هستش و یک رمزعبور قوی عبارتیست که شامل اعداد، حروف بزرگ و کوچیک، کاراکترهای ویژه و ... میشه.پس شما به عنوان برنامه‌نویس یا توسعه‌دهنده یک اپلیکیشن باید این قابلیت رو در برنامه خودتون قرار بدید تا به کاربر یادآوری کنه که پسوردی که در نظر داره باید شامل یک سری شرایط و حداقل‌های مدنظر شما باشه.من عارف مظفری از تیم زنیاک امروز اینجام که در این مقاله پلاگین Flutter Password Validator رو معرفی کنم که این کار رو برای دوستان فلاتر کاری که قصد پیاده‌سازی همچین قابلیتی در اپلیکیشن خودشون دارن انجام میده و میتونه به اون‌ها در توسعه سریع پروژه کمک بکنه.حداقل ۸ کاراکتر، ۲ حرف بزرگ، ۳ کارکاتر عددی و ۱ کاراکتر ویژهو اما روش استفاده۱- اضافه کردن پلاگیناون رو به وابستگی‌ها در pubspec.yaml اضافه کنید:dependencies:
  flutter_pw_validator: ^1.0.1۲- نصبپلاگین رو با استفاده از این دستور در ترمینال، دریافت(و البته نصب) کنید:flutter pub get۳- چطور استفاده میشهاول پلاگین رو با استفاده از این خط کد به اون فایلی در پروژه که نیاز به استفاده از پلایگن داره اضافه کنید:import &#039;package:flutter_pw_validator/flutter_pw_validator.dart&#039;; و در نهایت ویجت FlutterPwValidator رو درست در زیر تکست‌فیلد پسوردتون (جایی‌که قراره از کاربر پسورد موردنظرش رو دریافت کنید) قرار بدید و دقیقا همون کنترلر مورد استفاده رو هم در اختیار این ویجت بزارید:new TextField(
    controller: _passwordController
),
new FlutterPwValidator(
    controller: _passwordController,
    minLength: 6,
    uppercaseCharCount: 2,
    numericCharCount: 3,
    specialCharCount: 1,
    width: 400,
    height: 150,
    onSuccess: yourCallbackFunction
)و تمام. تمام کاری که نیاز که انجام بدید همین بود.اما جدول ورودی‌های این ویجت به این صورت که میتونید مقادیر دلخواه رو برای ظاهر و عملکرد ویجت به اون پاس بدید:جدول ورودی‌های این پکیجامیدوارم که این پلاگین بسیار مفید فایده باشه. در ضمن لینک گیت‌هاب این پلاگین در اکانت خودم و همینطور لینک خود پکیج رو در PubDev همین پایین قرار میدم که شامل یک پروژه مثال هم میشه: https://pub.dev/packages/flutter_pw_validator  https://github.com/ArefMozafari/flutter_pw_validator </description>
                <category>Xeniac</category>
                <author>عارف مظفری</author>
                <pubDate>Sun, 21 Feb 2021 13:50:49 +0330</pubDate>
            </item>
                    <item>
                <title>یونیت تست در فلاتر: Business Component</title>
                <link>https://virgool.io/Xeniac/%DB%8C%D9%88%D9%86%DB%8C%D8%AA-%D8%AA%D8%B3%D8%AA-%D8%AF%D8%B1-%D9%81%D9%84%D8%A7%D8%AA%D8%B1-business-component-zebdwkiurgcl</link>
                <description>موضوع TDD و یا توسعه با رویکرد تست، در روند توسعه‌ی یک برنامه همیشه بخش جدایی ناپذیری از علم برنامه‌نویسی بوده و ما امروز در این نوشته به بررسی نحوه تست‌نویسی در اپلیکیشن‌های فلاتری می‌پردازیم. در حال حاضر مهم‌ترین پکیج‌هایی که در این رابطه به کمک ما خواهند اومد پکیج‌های test و flutter test هستن که بخش عمده‌ای از کار رو برای ما آسون میکنن.در این مقاله ما یاد میگیریم که چطور برای بخش‌هایی (فانکشن‌هایی) از برنامه‌ی خودمون که جزئی از Business Logic نرم‌افزار ما هستنن، یونیت تست‌هایی در قالب درست و مفید پیاده کنیم.در رابطه با Business Components یا اجزای تجاری در کدهای یک برنامه باید گفت: به اون قسمت‌هایی از تکه کد‌های نوشته شده اطلاق میشه که وظیفه‌ی کارکرد درست در پلیکیشن رو برعهده دارن مثل محاسبات، ارتباط با سرور، ذخیره‌ی اطلاعات کاربر، نمایش دیتا درخواستی درست، تاریخچه و ... که همین موضوع نشون دهنده‌ی این مهم هست که یونیت‌تست‌های نوشته شده با این رویکرد اغلب از موضوع کارکرد درست ویجت‌ها و به شکل کلی، عملکرد UI اپلیکیشن مجزاست و به سراغ اون‌ها نمیره.خب بریم سراغ اصل مطلبفراهم کردن مبانیقبل از شروع به کار مطمئن بشید که اطلاعات قسمت dev_dependencies پروژه‌ شما در فایل pubspec.yaml به همین صورت باشه:dev_dependencies:
  flutter_test:
    sdk: flutterساختار فایل‌ها و پوشه‌هااز موارد مهمی که حتما باید حواسمون بهش باشه مسئله‌ی ساختار پوشه‌ها و فایل‌ها در پروژه‌ای هستش که در اون از یونیت‌تست استفاده میشه. همونطور که از اسمش پیداست unit test کارکرد صحیح هر یک از کوچیک ترین واحدهای اجرایی کدهای مارو در همه‌ی حالت‌های ممکن و انواع ورودی میسنجه پس مسئله‌ی دسترسی صحیح و البته سریع به هر یک از تست‌های نوشته شده برای بررسی‌های آینده‌ توسط توسعه‌دهنده بسیار مهم و حیاتی. از فواید رعایت این نکته در یک اپلیکیشن فلاتری میشه به موارد زیر اشاره کرد:جلوگیری از شلوغ شدن و به‌هم ریختگی در پوشه‌ی اصلی کدهای اپلیکیشن که درمورد فلاتر پوشه‌ی lib هست. دسترسی سریع به همه‌ی کدهای مربوط به بخش تست در صورت نیازآرامش ذهنی J و البته جابه‌جایی راحت در بین هر بخشپس در نهایت ساده‌ترین مثال از توضیحات بالا با رعایت ساختار درست در فایل‌های پروژه به صورت خواهد بود:lib/
      - xyz/
                - xyz.dart
 test/
        - xyz/
                - xyz_test.dart شکل کلی باید در هر دو پوشه‌ی test و lib  کاملا یکسان باشه. اگر هر قسمتی از کدهای Business Logic شما که در نظر دارید برای اون‌ها تستی نوشته بشه در فایلی به اسم xyz.dart در مسیر lib/path/to/xyz قرار گرفته باشن، کد‌های مربوط به یونیت‌تست‌های اون‌ها هم باید دقیقا به همون شکل در فایلی با نام xyz_test.dart در مسیر test/path/to/xyz جا بگیرن.برنامه حملهچارچوب درست تست نویسی یک سری قوانین رو در برمی‌گیره.از گیفی یک کلاس با نام Calculator رو به این صورت تصور کنید:class Calculator {
  int add(int a, int b) {
    return a + b;
  }  int subtract(int a, int b) {
    return a - b;
  }  int multiply(int a, int b) {
    return a * b;
  }  int divide(int a, int b) {
    return b == 0 ? 0 : (a ~/ b); // integer division
  }
}اگر چه که نوشتن تست‌های درست و البته به‌درد بخور به شکلی یک هنر کدنویسی محسوب میشه و باید با توجه به نیازهای پروژه بررسی بشه، اما توسعه‌دهنده‌های زیادی تا به حال سعی داشتن تا یک استاندارد و قاعده‌ی کلی رو برای درک و پیاده‌سازی بهتر تست‌نویسی جا بندازن که به شکل خلاصه از این باید‌ها پیروی میکنه:１. یک لیست از همه‌ی ویژگی‌های یک تکه کد یا Business Component مورد نظرتون برای نوشتن تست تهیه کنید.２. در این مرحله باید تلاش کنید که هر یک از ویژگی‌های لیستتون رو به مولفه‌های کوچیکتر (Subcomponent) بشکنید.３. مرحله‌ی دوم رو تا جایی ادامه بدید تا به نقطه‌ای برسید که کدتون از وضعیت فعلیش به بخش‌های کوچکتری قابل تقسیم نباشه. از این کار اصطلاحا با اسم atomization یا اتمی‌سازی میشه یاد کرد.４. بعد از این که از قدم قبلی سربلند بیرون اومدید حالا باید دونه به دونه subcomponentهارو انتخاب کنیم.５. حالا باید برای هر مولفه‌ی اتمی و کوچیکی که انتخاب کردیم فهرستی از انتظارت و یا خواسته‌هامون از اون subcomponent آماده کنیم.６. و در نهایت برای هر کدوم از اون خواسته‌ها و یا انتظارات کد تستمون رو بنویسیم.در ظاهر مقداری پیچیده به نظر میاد اما با پیاده‌سازی تست روی مثالی که در نظر گرفتیم این موضوع رو بهتر درک می‌کنیم.بریم برای نوشتن تستخب برگردیم به سراغ همون مثال کلاس ٰCalculator. ما چطور میتونیم اون رو تست کنیم؟با توجه به چهارچوبی که در بالا گفته شد در ابتدا باید لیستی از ویژگی‌های مد نظرمون از کامپوننت (در اینجا کلاس Calculator) تهیه کنیم.1. جمع کردن2. تفریق کردن3. ضرب کردن4. تقسیم کردناز همین اول میتونیم متوجه بشیم که هر کدوم از این ویژگی‌ها به‌خودی‌خود atomic هستن و نمیتونیم به بخش‌های کوچیک‌تری تقسیمشون کنیم پس در حال حاضر از مرحله‌ی ۲ و ۳ عبور کردیم.در ادامه ما باید فهرستی از انتظارات خودمون رو با به توجه به جزئیات لازم در رابطه با هر کدوم از این subcomponent ها آماده کنیم:1. جمع کردنجمع تو عدد صحیح مثبت به درستیجمع دو عدد صحیح منفی به درستیجمع یک عدد صحیح منفی و یک صحیح عدد مثبت به درستی2. تفریق کردنتفریق دو عدد صحیح مثبت به درستیتفریق دو عدد صحیح منفی به درستیتفریق یک عدد صحیح منفی و یک عدد صحیح مثبت به درستی3. ضرب کردنضرب دو عدد صحیح مثبت به درستیضرب دو عدد صحیح منفی به درستیضرب یک عدد صحیح منفی و یک عدد صحیح مثبت به درستی4. تقسیم کردنتقسیم دو عدد صحیح مثبت به درستیتقسیم دو عدد صحیح منفی به درستیتقسیم یک عدد صحیح منفی و یک عدد صحیح مثبت به درستیخب این شد لیست خواسته‌هایی که ما داریم از هر subcomponent که از شکستن ویژگی‌ها به دست اومد.و حالا به عنوان نمونه میریم به سراغ نوشتن یک یونیت‌تستِ مامان برای قابلیت جمع (add) در پروژه‌ی خودمون.محتویات فایل‌های تستاز گیفی خب تا اینجا از مقدمات یونیت‌تست و مفاهیم اولیه شنیدیم اما حالا چگونگی پیاده‌سازی اون رو به صورت عملی با هم بررسی می‌کنیم. با فرض این که شما فایلی به اسم calculator_test در پوشه‌ی مناسب ساختید،‌ ادامه‌ی این آموزش رو با هم پیش‌میریم.مطابق با رویه‌ای که تا به حال دربارش صحبت کردیم ما باید برای هر یک از انتظارات/خواسته‌هامون از متد مدنظر، کدِ تست جدایی رو پیاده کنیم. بنابراین طبق تحلیلی که ما از کدمون داریم باید ۴ تا تکه کدِ تستی برای ویژگی add از کلاس calculator خودمون بنویسیم. البته شاید سوال پیش‌بیاد که پس چرا ما ۳ تا مورد رو در بالا قید کردیم که درسته اما اباید بگم که در ینجا ما ترتیب اعداد منفی و مثبت در ورودی رو هم در نظر گرفتیم. به صورتی که شاید عدد منفی اول وارد بشه و عدد مثبت دوم و بالعکس که در نهایت میشه چهار حالت برای هر متد از کلاس calculator.خب برای نمونه کد تست ما به این شرح:test(&#039;should return a + b when a and b are two positives.&#039;, () {
  // arrange
  Calculator calculator = Calculator();
  int a = 10;
  int b = 20;
  int expectedResult = a + b;

  // act
  int actualResult = calculator.add(a, b);

  // assert
  expect(actualResult, expectedResult);
});در این جا از تابع test استفاده کردیم که به لطف استفاده از پکیج test اون رو در اختیار داریم.توضیح کد تستورودی اول تابع تست یک رشته هستش که توضیح مشخصی از آزمونی(تست) داره که ما در اینجا انجام دادیم. با دقت به تکه کد بالا میتونیم ببینیم که این String از یک قالب مشخص پیروی میکنه که به این شکل می‌باشد:should [expectation] when [condition].به این صورت که در جمله‌ی بالا به جای کلمه‌ی expectation انتظار خودمون در خروجی این تست و در قسمت condition وضعیت بخصوصی که ورودی‌های کد تحت تست ما در این حالت قرار دارن رو وارد می‌کنیم. در نهایت از این توضیح یک پیام واضح دریافت میشه که در این قالب حضور داره:The test component should do A when the condition is X.پارامتر دوم تابع test هم یک تابع callback هست که محتویات اصلی تست در اون قرار میگیره.بدنه‌ی تستباز هم در این قسمت یک الگوی مشخص برای پیروی وجود داره که با نام قرارداد AAA شناخته میشه. Arrange و Act و Assert.چینش و مرتب کردن (Arange)این بخش از بدنه‌ی تست جایی هستش که شما حقایق و در حقیقت مواد مورد نیاز تست خودتون رو کنار هم می‌چینید و یا به اصطلاح set up تست رو انجام میدید. میشه گفت که شما مقداردهی‌های اولیه‌ای که تست شما به اون‌ها نیاز داره رو در این قسمت نگهداری می‌کنید. خط آخر و مهم‌ترین کدی که در این قسمت حضور داره متغیر expectedResult هست که شما در اون مقداری رو ذخیره می‌کنید که از اجرای درست تست انتظار دارید. میشه گفت خروجی این تست از کد تحت آزمون شما در صورت سربلندی، باید مساوی با مقدار این متغیر باشه اینطوری شما متوجه می‌شید که بله! کد شما در این حالت ورودی، خروجی درست و مورد انتظار رو ارائه میده.انجام تست (Act)ما در اینجا به‌طور خلاصه عمل اصلی تست رو انجام میدیم و تابع خودمون رو صدا میزنیم. و مقدار بازگشتی اون رو در متغیری به نام actualResult ذخیره می‌کنیم. در واقع اینجاست که ما تست خودمون رو انجام میدیم تا ببینیم مقداری که از تابع (subcomponent) خودمون برگشته با این ورودی‌ها و شرایط بخصوص، ‌چه چیزی هست.اثبات ادعا (Assert)همون‌طور که از اسم این قسمت مشخصِ شما در اینجا با مقایسه‌ی دو مقدار ”مورد انتظار” و”فعلی” (expectedResult and actualResult) ادعای خودتون مبنی بر درست کارکردن تیکه کدتون ثابت می‌کنید. اگر که مقدار متغیر expectedResult (نتیجه‌ی مورد انتظار) برابر با مقدار متغیر actualResult (نتیجه فعلی) باشه نشون‌دهنده‌ی کارکرد درست subcomponent هست و همونطور که انتظار می‌رفت مقدار بازگشتی صحیح رو در اختیار شما قرار داده که در این صورت تست شما با موفقیت انجام شده.شما این مقایسه رو میتونید در تمامی حالات مساوی بودن، نامساوی بودن،‌ بزرگتر و کوچیکتر بودن و ... انجام بدید.استفاده از الگوی AAA کمک شایانی به وضوح تست شما داره و به شکلی شما رو مجبور به تفکر عینی می‌کنه. پس در نهایت همونطور که باید اصول کدنویسی تمیز و منظم رو در کدهای اصلی برنامه‌ی خودمون در نظر بگیریم، تست‌های نوشته شده هم برای فهم بهتر و کارایی درست باید مرتب و طبق اصول رسمی پیاده‌سازی شده باشن.گروه‌بندی تست‌هاخب پس فهمیدیم که ما برای هر وضعیت مورد انتظار از متد add باید یک تست جدا بنویسیم که در کنار هم ۴ تست مجزا رو خواهیم داشت. به نظرتون عالی نمیشد اگر که ما این قابلیت رو داشتیم تا به شکلی این تست‌ها رو گروه‌بندی کنیم؟ آفرین. صددرصد شما این ابزار مهم رو در اختیار دارید. با استفاده از تابع group که به همین منظور در پکیج test قرارداده شده شما می‌تونید تست‌های مرتبط به یک  subcomponent رو در یک گروه مشخص قرار بدید و میتونید روش استفاده از اون رو در زیر مشاهده کنید:group(&#039;tests for add component&#039;, () {
  test(&#039;should return a + b when a and b are two positives.&#039;, () {
    Calculator calculator = Calculator();
    // arrange
    int a = 10;
    int b = 20;
    int expectedResult = a + b;

    // act
    int actualResult = calculator.add(a, b);

    // assert
    expect(actualResult, expectedResult);
  });
});تابع group هم درست مثل قبل دو پارامتر، یکی به شکل رشته برای توضیحات و یکی هم شامل بدنه‌ی اصلی دریافت می‌کنه. قالب توضیحات هم بهتره که برای رعایت استاندارد به این شکل باشه:tests for [component name]که باید اون رو به این صورت بخونید:A group of tests for XYZ component.پس در نهایت یک مجموعه تست مناسب و کاربردی برای متد add ما به این صورت میشه:group(&#039;tests for add component&#039;, () {
  test(&#039;should return a + b when a and b are two positives.&#039;, () {
    // arrange
    Calculator calculator = Calculator();
    int a = 10;
    int b = 20;
    int expectedResult = a + b;

    // act
    int actualResult = calculator.add(a, b);

    // assert
    expect(actualResult, expectedResult);
  });

  test(&#039;should return a + b when a and b are two negatives.&#039;, () {
    // arrange
    Calculator calculator = Calculator();
    int a = -10;
    int b = -20;
    int expectedResult = a + b;

    // act
    int actualResult = calculator.add(a, b);

    //assert
    expect(actualResult, expectedResult);
  });

  test(&#039;should return a + b when a is negative and b is positive&#039;, () {
    // arrange
    Calculator calculator = Calculator();
    int a = -10;
    int b = 20;
    int expectedResult = a + b;

    // act
    int actualResult = calculator.add(a, b);

    //assert
    expect(actualResult, expectedResult);
  });

  test(&#039;should return a + b when a is positive and b is negative.&#039;, () {
    // arrange
    Calculator calculator = Calculator();
    int a = -10;
    int b = -20;
    int expectedResult = a + b;

    // act
    int actualResult = calculator.add(a, b);

    //assert
    expect(actualResult, expectedResult);
  });
});به علاوه، بدنه‌ یک گروه از تست‌ها میتونه میزبان ۲ تابع دیگه به نام‌های setUp و teardown باشه.تابع setUp به عنوان ورودی یک تابع callback رو با نوع بازگشتی void میپذیره که قبل از اجرای هر تست از اون گروه اجرا میشه.همین حالا احساس نیاز به استفاده از این تابع در کد خودمون مشهود و مشخص هستش. هرکدوم از تست‌های ما به صورت تکراری حاوی کدی هستن که یک نمونه (instance) از کلاس Calculator برای ما میسازه. این کد و امثال اون دقیقا همون‌هایی هستن که باید در تابع setUp قرار بگیرن تا از تکرار غیر ضروری برخی کدها جلوگیری بشه.setUp(() {
  calculator = Calculator();
});همچنین tearDown هم درست مثل قبلی یک تابع از نوع void رو دریافت می‌کنه با این تفاوت که اجرای این تابع بعد از اتمام هر تست از یک گروه از تست‌ها انجام میشه. میشه از این تابع برای اجرای کد‌هایی استفاده کرد که عمل بازگردانی بعضی موارد به حالت قبل رو برعهده دارن تا تست جدید در شرایط یکسان با تست قبلی انجام بشه.tearDown(() {
  calculator = Calculator();
});در آخرو اما در آخر کد نهایی ما برای تست کامل متد add به این صورت خواهد بود:import &#039;package:test/test.dart&#039;;

void main() {
  group(&#039;tests for add component&#039;, () {
    Calculator calculator;
    
    setUp(() {
      calculator = Calculator();
    });

    test(&#039;should return a + b when a and b are two positives.&#039;, () {
      // arrange
      int a = 10;
      int b = 20;
      int expectedResult = a + b;

      // act
      int actualResult = calculator.add(a, b);

      // assert
      expect(actualResult, expectedResult);
    });

    test(&#039;should return a + b when a and b are two negatives.&#039;, () {
      // arrange
      int a = -10;
      int b = -20;
      int expectedResult = a + b;

      // act
      int actualResult = calculator.add(a, b);

      //assert
      expect(actualResult, expectedResult);
    });

    test(&#039;should return a + b when a is negative and b is positive&#039;, () {
      // arrange
      int a = -10;
      int b = 20;
      int expectedResult = a + b;

      // act
      int actualResult = calculator.add(a, b);

      //assert
      expect(actualResult, expectedResult);
    });

    test(&#039;should return a + b when a is positive and b is negative.&#039;, () {
      // arrange
      int a = -10;
      int b = -20;
      int expectedResult = a + b;

      // act
      int actualResult = calculator.add(a, b);

      //assert
      expect(actualResult, expectedResult);
    });    tearDown(() {
      // Nothing to do here. 
    });
  });
}تست‌نویسی در فلاتر بسیار راحته و اگر که مثل من از VS Code استفاده می‌کنید این اسکیرین شات می‌تونه براتون سرشار از حس خوب و لبخند رضایت باشه.تیک‌های سبز دوست داشتنی!!!در ضمن تمامی کد‌هایی که توضیح داده شد در اینجا قرار گرفتن تا بتونید راحت‌تر از اون‌ها استفاده کنید.ممنون از این که این مقاله رو تا انتها مطالعه کردید و امیدوارم که حتما به دردتون خورده باشه، چرا که ما هم در تیم زنیاک خوشحال میشیم تا کمکی هرچند کوچیک به جامعه‌ی بزرگ توسعه‌دهندگان ایرانی داشته باشیم. هر سوال و یا نظری در ذهنتون داشتید خوشحال میشیم که در قسمت کامنت‌های همین پست اون‌هارو با ما درمیون بزارید.منبع</description>
                <category>Xeniac</category>
                <author>عارف مظفری</author>
                <pubDate>Sun, 09 Aug 2020 07:14:07 +0430</pubDate>
            </item>
                    <item>
                <title>نقشه راهی برای تبدیل شدن به یک توسعه دهنده فلاتر</title>
                <link>https://virgool.io/Xeniac/%D9%86%D9%82%D8%B4%D9%87-%D8%B1%D8%A7%D9%87%DB%8C-%D8%A8%D8%B1%D8%A7%DB%8C-%D8%AA%D8%A8%D8%AF%DB%8C%D9%84-%D8%B4%D8%AF%D9%86-%D8%A8%D9%87-%DB%8C%DA%A9-%D8%AA%D9%88%D8%B3%D8%B9%D9%87-%D8%AF%D9%87%D9%86%D8%AF%D9%87-%D9%81%D9%84%D8%A7%D8%AA%D8%B1-c8rup1enxz4f</link>
                <description>نقشه راهی برای تبدیل شدن به یک توسعه دهنده فلاتردر دنیای امروز اکثر افراد ترجیح میدن تا کمتر از کامپیوترشون استفاده کنن و بیشتر کارها مثل وبگردی، خوندن اخبار، چت و... رو با موبایلشون انجام بدن و دیگه تقریبا هر کسب و کاری به فکر ساخت و توسعه یه نرم افزار موبایل افتاده و اهمیت بیشتری هم نسبت به توسعه نسخه موبایل نرم افزارشون نسبت به سایر نسخه‌های وب و دسکتاپ میدن.از اونجایی که اپ‌های موبایل از اهمیت زیادی برخوردار هستن و خود من هم  به عنوان یک توسعه دهنده موبایل تجربه خوبی از توسعه نرم افزار با فلاتر داشتم، تصمیم گرفتم تا یه مقاله درباره &quot;نقشه راهی برای تبدیل شدن به یک توسعه دهنده فلاتر&quot; بنویسم.فلاتر چی هست اصلا؟کاربران موبایل توقع دارن تا نرم افزاری‌ که باش کار می‌کنن، رابط کاربری زیبا، انیمیشن‌های روون و پرفرمنس خوبی داشته باشه، پس توسعه دهنده‌های اپلیکیشن‌های موبایل باید تموم سعیشون رو بکنن تا تموم این ویژگی‌ها رو خیلی خوب و سریع بدون اینکه به کیفیت نرم افزار آسیبی بزنه، پیاده کنن و همین موجب شد تا گوگل به فکر ساخت یه فریم ورک جدید به اسم فلاتر بیوفته.فلاتر (Flutter) یک واسط کاربری کیت توسعه نرم‌افزار متن‌باز است که توسط گوگل طراحی و ساخته شده‌است. فلاتر برای توسعه برنامه‌های کاربردی برای پلتفرم‌های اندروید، آی‌اواس، ویندوز، مک‌اواس، لینوکس، گوگل فیوشا و وب استفاده می‌شود.- ویکی‌پدیاحالا چرا فلاتر؟حتما از این موضوع مطلعید که بجز فلاتر، فریم ورک‌های مختلف دیگه‌ای هم برای توسعه اپ‌های Cross-platform وجود داره. ولی فلاتر چه ویژگی‌‎هایی داره که خودشو نسبت به بقیه فریم ورک‌ها متمایز کرده؟توسعه سریعرابط کاربری زیبا و گویاعملکرد نیتیو (بومی)این 3 ویژگی اصلی فلاتر هستن که هرکدوم رو بطور خلاصه در ادامه توضیح میدیم.توسعه سریعفلاتر از ویژگی‌ای به اسم Hot reload برخورداره که به شما کمک می‌کنه تا خیلی سریع‌تر بتونید رابط کاربری رو پیاده کنید و تغییرات مورد نیاز رو انجام بدید و به علاوه باگ‌ها رو سریع‌تر رفع کنید. همزمان با کدنویسی هم می‌تونید خروجی مدنظرتون رو بدون اینکه مجبور به اجرای مجدد برنامه باشید، مشاهده کنید. این قابلیت در نهایت کمک می‌کنه تا از هدر رفت وقتتون جلوگیری کنید.Fast developmentرابط کاربری زیبا و  گویاهمه چیز در فلاتر ویجته و هر ویجت هم از ویجت‌های مختلف دیگه‌ای تشکیل شده. همین ویژگی به برنامه‌نویس این امکان رو میده تا بتونه خیلی راحت و سریع رابط کابری مورد نیازش رو پیاده سازی کنه.Expressive, beautiful UIsعملکرد نیتیو (بومی)تمامی کدهای نوشته شده در فلاتر به زبان Dart هستن که موقع اجرا به زبان نیتو اون پلتفرم کامپایل می‌شن که این امر موجب میشه تا برنامه ما همون سرعت و پرفورمنس نرم افزارهای بومی را داشته باشه.Native Performanceیادگیری فلاتر رو از کجا و چطوری شروع کنیم؟فلاتر سریع و بسیار راحته و اگه هم تجربه کار با زبان Java یا هر زبان شیءگرا دیگه‌ای رو داشته باشید، دیگه کارتون خیلی راحت‎ترم میشه. ولی با این حال توصیه می‌کنم که قبل از اقدام به یادگیری فلاتر، آموزشی مقدماتی از Dart ببنید تا اون دانش پایه از این زبان رو داشته باشید.با یه سرچ ساده متوجه میشید که چقدر آموزش‌های مختلف غیر رایگان و رایگانی، برای Dart و Flutter وجود داره که اگر با ویدیو راحت‌ترید، یوتیوب پر از آموزش‌های با کیفیت و واقعا خوبه. درنهایت اگه هم بیشتر اهل مطالعه هستید، مدیوم می‌تونه یه منبع پر از مقالات و آموزش‌های عالی براتون باشه.من خودم اولین بار فلاتر رو از دوره آموزشی فلاتر راکت یاد گرفتم که واقعا دوره کاربردی و نسبتا کاملی بود. التبه تو همین ویرگولم می‌تونید مقالات و آموزشای جالبی در مورد Dart و Flutter پیدا کنید.فلاتر چطوری کار می‌کنه؟بطور خیلی خلاصه بخوام بگم، تمامی کدهایی که ما به زبان Dart می‌نویسیم از طریق روش (AOT (Ahead of time، مستقیما به زبان نیتو کامپایل میشن که دلیلشم اینکه iOS اجازه کامپایل به روش پویا رو نمیده.اگه دوست داشتید می‌تونید اینجا بیشتر در موردش بخونید.نصب و شروع کار با فلاتر 1. نصباول از همه باید SDK فلاتر رو دانلود و نصب کنید.داکیومنت وبسایت فلاتر به شکل کاملی و بسته به سیستم عاملتون نحوه نصبش رو توضیح داده. همچنین، تقریبا اولین بخش از هر دوره آموزشی هم به این مورد اختصاص داده شده.2. راه‌اندازی ویرایشگربا هر ویرایشگری که دارای ابزار Command-line یا همون خط فرمان باشه می‌تونید شروع به ساخت برنامه‌های فلاتری کنید. Android Studio ،IntelliJ ،VS Code و Emacs از محبوب‌ترین و پر استفاده‌ترین IDEها و ادیتورهایی هستن که برای ساخت اپ‌های فلاتر ازشون استفاده میشه. راه اندازیشونم بسیار سادست و فقط نیاز دارید تا پلاگین‌های مورد نیاز رو نصب کنید و بعد همه چی برای شروع یه سفر هیجان انگیز به همراه فلاتر آمادست :)داکیومنت راه اندازی ادیتور در وبسایت فلاتر3. ساخت اولین پروژه فلاتردو روش برای ایجاد پروژه جدید در فلاتر وجود داره: استفاده از IDE و یا استفاده از Command-line.اگه می‌خواید با IDE، پروژه‌ی جدیدتون رو بسازید توضیح خاصی نیاز نداره. ولی اگه دوست دارید که از طریق Command-line این کارو انجام بدید، از دستور زیر استفاده کنید:flutter create &lt;project_name&gt;4. نمای کلی پروژهبعد از اینکه پروژه فلاتر رو ساختید، فولدرها و فایل‌هایی مثل عکس پایین رو میبینید. اکثر کدهای نوشته شده به زبان دارت در فولدر lib قرار داده میشن و کدهای نیتیو هم در فودرهای android و ios موجود هستن.نمای کلی پروژه5. اجرای اولین برنامهاپ‌هاتون رو می‌تویند هم روی دستگاه‌ فیریکی مثل گوشی خودتون و هم شبیه‌سازهایی مثل AVD و ... اجرا کنید.پس از اتصال دستگاه، از یکی از روش‌های زیر می‌تونید استفاده کنید تا اپلیکیشن رو ران کنید.1. استفاده از دکمه یا میانبر Run در ادیتور.2. اجرای دستور flutter run در Command-line.وقتی که پروژه فلاتر جدیدی رو ایجاد می‌کنید، می‌بینید که از قبل کدهایی وجود داره و با اجرا اونها، یک برنامه شمارنده خیلی ساده اجرا میشه.خورجی اپ شمارنده فلاتراین کدها فقط یک مثال خیلی خیلی ساده از فلاتره و نیازی نیست که نگرانشون باشید.شما برای ساخت اپ خودتون این کدهارو پاک می‌کنید تا کدهای خودتون رو جایگزین کنید.ویجت‌ها در فلاترهمونطوری که قبلا هم گفتم، همه چیز در فلاتر ویجت هستش و هر کدوم از اون‌ها هم از ویجت‌های دیگه‌ای تشکیل شده. پس هرچیزی که تو فلاتر می‌بینید، در واقع یک ویجته. پس اگر می‌خواید با فلاتر کار کنید، باید با ویجت‌های اون آشنایی پیدا کنید.برای آشنایی بیشتر می‌تونید لیست این ویجت‌ها رو از اینجا ببنید. همچنین، پیشنهاد می‌کنم که ویدیوهای منتشر شده در پلی لیست Widget of the week رو هم مشاهده کنید. تیم فلاتر هر هفته یکی از این ویجت‌هارو در قالب یک ویدیوی کوتاه 1 دقیقه‌ای توضیح میده. حرف پایانیدرسته که فلاتر یک تکنولوژی جدیده و نسبت به سایر فریم ورک‌های مشابه از توسعه دهندگان کمتری برخورداره، ولی سرعت رشدش به شدت بالاست. بطوریکه در آینده‌ای نه چندان دور، جامعه توسعه دهندگان اون از سایر فریمورک‌های مشابه مثل React Native و Xamarin بیشتر خواهد بود. همین الانش هم فلاتر کامیونیتی بزرگی داره و هر آموزش یا سوالی که داشتید باشید رو می‌تونید با یه جست‌وجوی ساده پیاده کنید. پس از این بابتم جای نگرانی نیست.به عنوان کسی که از برنامه‌نویسی نیتیو اندروید به سمت فلاتر اومده، باید بگم که درسته که فلاتر هم مثل هر تکنولوژی دیگه‌ای مشکلات مربوط به خودش رو داره، اما خیلی بیشتر از محیط نیتیو ازش راضی هستم و از اینکه این تصمیم رو گرفتم به هیچ عنوان پشیمون نیستم.امیدوارم که این مقاله براتون مفید بوده باشه. اگر سوالی راجب فلاتر داشتید، اون رو برام کامنت کنید. خوشحال میشم کمکتون کنم.</description>
                <category>Xeniac</category>
                <author>یوسف روشندل</author>
                <pubDate>Wed, 29 Apr 2020 22:55:15 +0430</pubDate>
            </item>
                    <item>
                <title>ساخت modal های داینامیک در vue راحت تر از همیشه!</title>
                <link>https://virgool.io/Xeniac/%D8%B3%D8%A7%D8%AE%D8%AA-modal-%D9%87%D8%A7%DB%8C-%D8%AF%D8%A7%DB%8C%D9%86%D8%A7%D9%85%DB%8C%DA%A9-%D8%AF%D8%B1-vue-%D8%B1%D8%A7%D8%AD%D8%AA-%D8%AA%D8%B1-%D8%A7%D8%B2-%D9%87%D9%85%DB%8C%D8%B4%D9%87-n0ygaqmloqwz</link>
                <description>سلام. امیدوارم حالتون خوب باشههمه ی ما، توی پروژه های مختلف مجبور شدیم از modal استفاده کنیم. این modal ها معمولا توی یک تمپلیت و قالب خاصی طراحی شدن و دست برنامه نویس برای تغییرات و شخصی سازی کردن اون ها، تا حدودی بسته میشه.xmodal v 0.1.0 ?ما برای رفع این مشکل دنبال راهی بودیم که بتونیم کامپوننت های خودمون رو به صورت داینامیک و بدون محدودیت با توجه دیزاین متناسب با وبسایت خودمون، به عنوان modal استفاده کنیم. این ایده باعث شد که ما پکیج xmodal رو طراحی و پیاده سازی کنیم!این پکیج بهتون کمک میکنه تا از شر modal های زشت ? از پیش طراحی شده خلاص بشید و کامپوننت خودتون رو رو به عنوان modal، در پروژتون استفاده کنید.? درواقع xmodal یک چارچوب و wrapper در اطراف کامپوننت های شماست که اون رو به صورت یک modal توی صفحه مورد نظر، بارگذاری میکنه.به همین راحتی شما میتونید modal های اختصاصی خودتون رو داشته باشید.مخزن گیتهاب / پیش نمایشxmodal demoاستفاده از این پکیج خیلی راحته، تنها کاری که باید بکنید اینه که یک کامپوننت طراحی بکنید و اون رو به پکیج معرفیش کنید. ( رفرنس بدید ) بقیه کارا به صورت اتوماتیک انجام میشه و حالا شما میتونید از کامپوننت مورد نظرتون به صورت یه modal استفاده کنید. ( مثل گیف بالا )امکانات این پکیج:قابلیت اضافه کردن کامپوننت ها به صورت داینامیک ?قابلیت کنترل و تغییر و اضافه کردن animation برای کامپوننت مورد نظر ?️قابلیت اضافه کردن تایمر برای کنترل کردن زمان بسته شدن کامپوننت ⏰قابلیت قفل کردن (Disable) کامپوننت و باز بسته کردن اون بر اساس شروط v-if v-else ?کنترل کردن کامپوننت و تنظیمات اون از طریق global fuctions در هرجای برنامه ?قابلیت ست کردن تنظیمات پیش فرض برای همه modal ها ?قابلیت ارسال اطلاعات به کامپوننت از طریق props ?قابلیت های شخصی سازی رنگ پس زمینه و میزان شفافیت بکگراند modal ?و...این پکیج در npm موجوده و میتونید با دستورات زیر نصبش کنیدnpm i xmodal-vue
// or
yarn add xmodal-vueبرای خوندن داکیومنت هم میتونید به مخزن گیت هاب مراجعه کنید. اینجا سرتون رو درد نمیارم. مخزن گیتهاب / پیش نمایشممنونم که وقت گذاشتید و این مطلب رو تا آخر خوندید.HAPPY CODING ?</description>
                <category>Xeniac</category>
                <author>Mahdi fakhr</author>
                <pubDate>Tue, 07 Apr 2020 09:29:53 +0430</pubDate>
            </item>
                    <item>
                <title>نسخه 2 وب‌سایت زنیاک منتشر شد!</title>
                <link>https://virgool.io/Xeniac/%D9%86%D8%B3%D8%AE%D9%87-2-%D9%88%D8%A8%D8%B3%D8%A7%DB%8C%D8%AA-%D8%B2%D9%86%DB%8C%D8%A7%DA%A9-%D9%85%D9%86%D8%AA%D8%B4%D8%B1-%D8%B4%D8%AF-hiuap8xsjmyo</link>
                <description>Xeniac | Software Productionشاید اکثر شماهایی که دارید این متن رو می‌خونید، با زنیاک آشنا نیستید و شاید اسمش رو هم نشنیدید. پس به همین دلیل، بهتره که قبل از هرچی زنیاک رو بهتون به صورت خیلی خلاصه معرفی کنم.زنیاک چیست یا بهتره بگیم کیست؟زنیاک (Xeniac) یک تیم برنامه نویسی نو ظهوره که حدودا از پارسال توسط چندتا از توسعه دهندهای وب و موبایل و طراح‌ها رابط کاربری تشکیل شد. هدفمون هم ارائه‌ی ایده‌های نو و خلاقانه در زمینه‌ی طرح‌ها و پیاده‌سازی‌ محصولات دیجیتال و نرم افزاری به بهترین شکل ممکن بوده که خب خوشبختانه میشه این مورد رو در تمامی پروژه‌هایی که تا به اینجای کار پیاده کردیم رو مشاهده‌ کرد.همه چیز درباره نسخه دوم سایتصفحه ایندکسچی شد که به فکر نسخه جدیدی از سایت رو افتادیم؟!خب، نسخه قبلی سایت که اولین نسخه هم بود، خیلی ساده بودش. در حدی که فقط داخل اون نمونه کارهامون رو گذاشته بودیم، اعضای تیم رو معرفی کرده بودیم (اونم فقط با عکس و مهارتمون و البته اسممون) و اطلاعات تماس. برای قسمت سفارش پروژه هم یه چیزی شبیه به Google Forms پیاده کرده بودیم.در کل سایت قشنگ و جمعوجوری بود ولی اون چیزی که ما می‌خواستیم و البته بهش نیاز داشتیم نبود. پس در نهایت به فکر طراحی و پیاده سازی نسخه جدیدی از سایت افتادیم که هم نیازهای خودمون رو پوشش بده و در عین حال امکانات ویژه‌ای برای کلاینت‌هامون فراهم می‌کنه که البته این خودش نشون دهنده گوشه کوچیکی از مهارت‌های ماست ?. تعدادی از اصلی‌ترین امکانات نسخه جدید سایتدر نسخه جدید وب‌سایتمون، تلاش شده تا نهایتا تمام بخش‌ها و مراحل لازم از معرفی و شناخت تا سفارش پروژه و موارد مربوط به اون رو در سایتمون قرار بدیم. این یعنی وب‌سایت جدید زنیاک دیگه تنها یک سایت معمولی برای معرفی اون نیست، بلکه حالا با امکانات جدید اضافه شده، به یک وب‌سایت کامل برای سفارش و پیگیری پروژه شما تبدیل شده. تو این قسمت هم بیشتر همین ویژگی‌های جدید رو بطور خلاصه معرفی می‌کنم.بخش‌های مربوط به نمونه کارهااگر به عنوان یک مشتری بخواید محصولی رو به تیم یا شرکتی سفارش بدید، قطعا اولین کاری که نیازه اینه که راجب اون تیم یا شرکت تحقیق کنید و نمونه کارهاشون رو ببینید. اینطوری یه شناخت نسبتا خوبی راجب بهشون پیدا می‌کنید.برای همین ماهم بخش‌هایی مثل خدمات، درباره ما و نمونه کارها برای معرفی تیم و صفحه‌هایی مثل شرایط انجام پروژه و نمونه قرارداد رو هم برای آشنایی بیشتر با نحوه و روند انجام کارمون در سایت قرار دادیم.صفحه نمونه کارهاثبت پروژه:به عنوان یک مشتری خیلی راحت می‌تونید از این بخش با وارد کردن اطلاعات خواسته شده مثل نام و شماره تماس و یه مختصر توضیحی راجب چیزی که در ذهن دارید، پروژه جدیدی رو سفارش بدید و ماهم پس از بررسی با شما برای سایر مراحل و صحبت‌های بیشتر درباره پروژه تماس می‌گیریم.صفحه ثبت پروژهپنل کاربری:میشه گفت که پنل کابری یکی از کلیدی‌ترین و مهم‌ترین بخش‌های اضافه شدست. مشتری‌های عزیزمون پس از بستن قرار داد می‌تونن از این قسمت با استفاده از کد پیگری که در اختیارشون قرار میگیره وارد پنل اختصاصی خودشون بشن و مراحل پیشرفت پروژه رو در لحظه پیگیری کنن، از نتایج جلسات اسکرام مطلع بشن و با اعضای تیم و مسئول هر فاز از پروژه به صورت مستقیم در تماس باشن.پنل کاربریتکنولوژی‌های استفاده شدهFront-end: Vue.js - Nuxt.jsBack-end: Node.jsو اما صفحه‌ی سوالات متداول هم برای اون دسته از عزیزانی طراحی شده که نیاز به یک سری اطلاعات جزئی‌تر در رابطه با شکل کار زنیاک (مثل تکنولوژی‌ها و چارچوب‌های کلی پیاده‌سازی) تو ذهنشون هست تا خیلی سریع به جواب اون‌ها برسن.سخن آخرامروز خیلی خوشحالیم از این که خبر آماده شدن نسخه‌ی جدید سایت تیم رو بدیم. زحمات زیادی براش کشیده شد که نتیجه‌ی اون البته، ختمِ به این عملکرد قابل افتخاره.خوشحال می‌شیم اگه به وب‌سایتمون سر بزنید و با نقطه‌ نظرات سازندتون تیم ما رو در مسیر ارائه‌ی خدمات بهتر یاری کنید.ما بچه‌های زنیاک با همین سهم کوچیکمون از جامعه‌ی توسعه‌دهندگان کشور عزیزمون ایران، از صمیم قلب امیدواریم که سال جدید برای همه‌ی ما در عین مشکلات پیش اومده تا به اینجا، پر از اخبار عالی و موفقیت‌های سرشار در طول خودش باشه.سرحال و سرزنده باشید. سال نوتون هم مبارک!</description>
                <category>Xeniac</category>
                <author>یوسف روشندل</author>
                <pubDate>Fri, 03 Apr 2020 17:19:01 +0430</pubDate>
            </item>
                    <item>
                <title>3 خط فرمان مفید برای تست سرعت اینترنت</title>
                <link>https://virgool.io/Xeniac/3-handy-command-line-internet-speed-tests-u9ba9ffyesbs</link>
                <description>3 خط فرمان مفید برای تست سرعت اینترنتباخبر بودن از سرعت اتصالمون به اینترنت، کمکمون می‌کنه تا بهتر کامپیوترمونو کنترل کنیم. برای همین امروز، تو این مقاله می‌خوام 3 تا Command-line (خط فرمان) برای تست سرعت اینترنت رو معرفی کنم که هر 3 تاشون رایگان و اپن سورس هستن.Speedtestفکر کنم دیگه هممون با Speedtest آشنا هستیم و حداقل برای یه بارم که شده ازش استفاده کردیم. اما افراد کمتری از Command-line این سرویس استفاده می‌کنند. Speedtest در پایتون اجرا میشه و در پکیج منیجرهای apt و pip موجوده و می‌تونید ازش به عنوان یک command-line tool و یا کتابخونه پایتون استفاده کنید.نصب از طریق:sudo apt install speedtest-cliیاsudo pip3 install speedtest-cliو سپس با دستور speedtest اجراش کنید.خروجی دستور speedtestاین دستور سرعت دانلود و آپلود اینترنتتون رو در اختیارتون میذاره و سریع و قابل اسکریپته و می‌تونید بطور مرتب اجراش کنید و خروجی رو در یک فایل و یا دیتابیس ذخیره کنید.FastFastخط فرمان بعدی Fast نام داره و توسط Netflix ارائه شده. آدرس نسخه وبش Fast.com هست و رابط خط فرمانشو هم می‌تونید از پکیج منیجر npm نصب کنید:npm install --global fast-cliبعد از نصب، با دستور fast اجراش کنید:خروجی دستور fastدستور بالا فقط سرعت دانلود رو نشونتون میده و اگه می‌خواید سرعت آپلود رو هم ببنید از fast --upload استفاده کنید:خروجی دستور fast --upload iPerfiPrefآخرین خط فرمانی که می‌خوام تو این مقاله معرفی کنم iPerf نام داره و بر خلاف 2 ابزار قبلی که برای تست سرعت اینترنت بودن، iPerf یک ابزار عالی برای تست سرعت LAN هستش.روی توزیع‌های Debian-based از طریق پکیج منیجر apt می‌تونید نصبش کنید:sudo apt install iperfهمچنین، برای ویندوز و مک هم موجود هستش.برای استفاده از این ابزار نیاز به دو تا سیستم دارید که روی هر دو باید iPerf نصب شده باشه و یکی از سیستم‌هارو به عنوان سرور تعیین کنید.برای بدست اوردن IP address سرور از خط فرمان زیر استفاده کنید:ip addr show | grep inet.*brdبا فرض اینکه از IPv4 به عنوان local network استفاده می‌کنیم، local IP address مون با 192.168 یا 10 شروع میشه. IP address رو یه جا یادداشت کنید تا بتونید روی بقیه دستگاه‌ها (سیستمی که به عنوان سیستم کاربر تعیین کردید) هم ازش استفاده کنید.دستور iperf -s رو در سرور اجرا کنید:iperf -sپس از اجرای دستور بالا، سرور منتظر ارتباط ورودی از سمت مشتری میمونه.حالا روی سیستم کاربر این دستور را اجرا کنید و IP address سرور رو مثل مثال زیر جایگزین کنید:iperf -c 192.168.1.2انجام هر تست فقط چند ثانیه طول میکشه که با هربار تست، می‌تونید اندازه انتقال و پهنای باند محاسبه شده رو مشاهده کنید.جمع بندیبه همین سادگی می‌تونید از سرعت اینترنت و شبکتون مطلع بشید. اگه فقط قصد دارید تا از سرعت اینترنتتون به هر دلیلی از طریق Command-line مطلع بشید، Speedtest و Fast دو ابزار سریع و ساده برای این کار هستن ولی اگه به دنبال تست شبکه point-to-point هستید، iPref ابزار مناسبی برای این کاره.اگه ابزار دیگه‌ای رو می‌شناسید و یا ازش استفاده می‌کنید، اونارو تو کامنت معرفی کنید.. منبع</description>
                <category>Xeniac</category>
                <author>یوسف روشندل</author>
                <pubDate>Mon, 17 Feb 2020 00:06:28 +0330</pubDate>
            </item>
                    <item>
                <title>جیگسا فریمورک دوست داشتی برای لاراولیا</title>
                <link>https://virgool.io/Xeniac/%D8%AC%DB%8C%DA%AF%D8%B3%D8%A7-%D9%81%D8%B1%DB%8C%D9%85%D9%88%D8%B1%DA%A9-%D8%AF%D9%88%D8%B3%D8%AA-%D8%AF%D8%A7%D8%B4%D8%AA%DB%8C-%D8%A8%D8%B1%D8%A7%DB%8C-%D9%84%D8%A7%D8%B1%D8%A7%D9%88%D9%84%DB%8C%D8%A7-yzaxb4w2xbdo</link>
                <description>اگه لاراول کاری و دلت میخواد یه Static Site Generators خودمونی داشته باشی Jigsaw بهتون پیشنهاد میکنم.جیکسا یه فریمورک برای تولید صفحات استاتیک که از موتور قالب بلید(blade) داره استفاده میکنه. استفاده ازش خیلی آسونه با چند تا کامند ساده سایت شما آمادست تا بتونید داکیومنت پروژتونو داخلش کامل کنید یا اینکه بلاگ خودتونو درست کنید.حالا این جیسگا چه قابلیتایی داره؟ظاهر کاملا ریسپانسیواز فریمورک Tailwind CSS استفاده میکنهاستفاده از ابزار PurgeCSSاسفتاده از کتابخونه highlight.jsفایل sitemap.xml هم براتون میسازهمیتونید صفحه ۴۰۴ تونم تغییر بدیدحالا میریم سراغ مزه کردن جیسکا :)۱-ساخت دایرکتوری پروژه:$ mkdir my-site۲-نصب جیکسا توسط کامپوزر:$ cd my-site$ composer require tightenco/jigsaw۳- آماده سازی پروژه:اینجا ما میتونیم از چندتا دستور مختلف استفاده کنیم.اگر میخواید پروژتون خیلی ساده باشه و از هیچ ظاهری برخودار نباشه(فقط واسه تست میخواید استفاده کنید) این دستور بزنید:$ ./vendor/bin/jigsaw initحالا اگه از جیگسا برای ساخت داکیومنت استفاده کنید از دستور زیر استفاده کنید:$ ./vendor/bin/jigsaw init docsظاهر سایتتون میشه شبیه این صفحه. یا اگه میخواید ازش به عنوان وبلاگ استفاده کنید از دستور زیر استفاده کنید:$ ./vendor/bin/jigsaw init blogخب اینجاهم ظاهر سایتتون میشه این شکلی.بعدش این دستور بزنید تا فایل هاتون ساخته بشن:$ ./vendor/bin/jigsaw buildدر آخر هم کامند زیر بزنید تا پروژتون روی آدرس localhost:8000 بیاد بالا:$ ./vendor/bin/jigsaw serveنتیجه گیری:این ابزار میتونه برای لاراول کارایی که میخوان مستندات api بزنن یا شاید بخوان یه سایت ساده بدون دیتابیس بسازن میتونه یه انتخاب معقولانه‌ای باشه.منبع</description>
                <category>Xeniac</category>
                <author>Mahdi Ebrahimi</author>
                <pubDate>Mon, 27 Jan 2020 00:53:05 +0330</pubDate>
            </item>
                    <item>
                <title>نحوه ارتقاء از ویندوز 7 به گنو/لینوکس</title>
                <link>https://virgool.io/Xeniac/how-to-upgrade-from-windows-7-to-linux-i1dbn8sqcmur</link>
                <description>نحوه ارتقاء از ویندوز 7 به گنو/لینوکساگه هنوز دارید از Windows 7 به دلیل اینکه از Windows 10 خوشتون نمیاد و یا سیستمی قدیمی‌‌ و ضعیف دارید استفاده می‌کنید، کاملا قابل قابل درک است. اما یک راه جایگزین برای ارتقاء سیستم‌عامل سیستمون وجود داره و اون استفاده از سیستم‌عامل گنو/لینوکس است. می‌تونید لینوکس رو به صورت رایگان روی کامپیوتر شخصیتون نصب کنید و از یک سیستم‌عاملی که پشتیبانی و آپدیت میشه بهره‌مند بشید!اینکار از اون چیزی که بهش فکر می‌کنید راحت‌تره. می‌تونید لینوکس رو قبل از نصب روی کامپیوترتون امتحان کنید و یا حتی در کنار ویندوز فعلیتون نصب کنید.یک جایگزین عالی برای ویندوز 7در سال 2020 عملکرد لینوکس از اونچه که فکر می‌کنید بسیار بهتر شده است. بخصوص اگه سیستمی قدیمی دارید که روی اون ویندوز 7 نصب دارید. سخت افزارتون به خوبی از لینوکس پشتیبانی خواهد کرد و بدون هیچ مشکلی روی اون اجرا خواهد شد. همچنین، با نصب درایورهای سخت‌افزارتون می‌تونید عملکرد بهتری تو اجرای بازی‌ها داشته باشید.توزیع مورد علاقتونو انتخاب کنید، اونو نصب کنید و تمام. به گنو/لینوکس خوش آمدید!قرار نیست تو این مقاله نحوه نصب گنو/لینوکس رو آموزش بدم. چون، برای این کار به اندازه کافی مقالات و فیلم‌های آموزشی، به هر زبانی، برای هر توزیعی که می‌خواید وجود داره و فقط یه گوگل ساده نیاز دارید.بعد از نصب لینوکس، حالا نوبت به نصب نرم افزارهای مورد نیازتون میشه که برا این کار روش‌های مختلفی وجود داره که بهترین و ساده‌ترین راه، استفاده از پکیج منیجرهای هر توزیع هستش. در ادامه 5 تا از پکیج منیجرهای معروف‌ترین توزیع‎های لینوکس رو نام میبریم:پکیج منیجر Pacman برای توزیع‌های Arch-based: مثل Arch و Manjaroپکیج منیجر DPKG برای توزیع‌های Debian-based: مثل Debian و Ubuntuپکیج منیجر RPM برای توزیع‌های RedHat: مثل Fedora و SUSE Linuxپکیج منیجر Zypper برای توزیع‌های openSUSE مثل openSUSEپکیج منیجر Portage برای توزیع‌های Gentoo مثل Gentoo Linux و Sabayon Linuxاما اگه نرم افزار و یا پکیج مورد نظرتون تو پکیج منیجر اصلی نبود، می‌تونید از سایر پکیج منیجرهای موجود مثل Snappy و... استفاده کنید. (ولی تا حد امکان سعی کنید از این پکیج منیجرها استفاده نکنید، چونکه زیاد معتبر نیستند.)توزیع‌های گنو/لینوکس رایگان و اپن سورس هستن، بصورت خودکار آپدیت‌های امنیتی رو دریافت می‌کنن و دیگه نیازی به نصب و استفاده از آنتی ویروس ندارید. فقط مثل هر سیستم‌عامل دیگه‌ای، مراقب باشید که هر نرم افزاری رو از هر جایی دریافت نکنید!نصب گنو/لینوکس در کنار ویندوز - Multi-bootingهمونطور که در اول مقاله هم گفتم، می‌تونید در کنار ویندوز فعلیتون، گنو/لینوکس هم نصب کنید. با انجام این کار هرباری که سیستمتون رو روشن می‌کنید، با صفحه‌ای روبه رو میشید که از اونجا سیستم‌عاملی که می‌خواید بوت کنید رو انتخاب می‌کنید.اینطوری، هروقت که نیاز داشته باشید دوباره از ویندوزتون استفاده کنید، فقط کافیه یکبار کامپیوتر رو ری‌استارت کنید و ویندوز رو بوت کنید.همونطور که می‌دونید، همه‌ی بازی‌ها و یا نرم افزارها برای لینوکس موجود نیستن و به همین دلیل شاید بعضی وقتا به ویندوز نیاز پیدا کنید. هرچند روش‌هایی برای اجرای بازی‌ها و نرم افزارهای ویندوز در لینوکس هم وجود داره.خب دیگه فکر کنم هرچی لازم بود رو گفتم و اگه سوالی داشتید، در بخش نظرات بپرسید، خوشحال میشم کمکتون کنم. اینم بگم که این اولین مقاله من تو ویرگول و دیگه اگه کمو کسری داشت ببخشید. ❤️ هم یادتون نره :)و! همچنین، بزودی یه مقاله مفصل هم درباره مهاجرت از ویندوز به لینوکس و تجربه‌های خودم قراره بنویسم.</description>
                <category>Xeniac</category>
                <author>یوسف روشندل</author>
                <pubDate>Sun, 19 Jan 2020 01:41:24 +0330</pubDate>
            </item>
            </channel>
</rss>