آموزش جامع Webpack (بخش ششم و پایانی: React Router) — از صفر تا صد
در بخش قبلی این سری مطالب با روش ایمپورت دینامیک و افراز کد در Webpack آشنا شدیم. در این بخش React و react-router را به اپلیکیشن خود اضافه میکنیم و مسیریابی را بر مبنای افراز کد در اپلیکیشن اجرا میکنیم. برای مطالعه بخش قبلی این سری مقالات روی لینک زیر کلیک کنید:
افزودن React به اپلیکیشن
در این بخش با روش اضافه کردن پشتیبانی JSX روی Babel آشنا میشویم.
از آنجا که وظیفه transpile کردن بر عهده Weback نیست و Babel این کار را انجام میدهد، باید کاری کنیم که بتواند JSX را بخواند و transpile کند.
yarn add @babel/preset-react –dev
این پکیج را به babelrc. اضافه میکنیم:
1{
2 "presets": ["@babel/preset-env", "@babel/preset-react"],
3 "plugins": ["@babel/plugin-syntax-dynamic-import"]
4}
اکنون که همه چیز تنظیم شده است، react را به عنوان یک وابستگی نصب میکنیم:
yarn add react react-dom
دستکاری Webpack
در این بخش برخی تغییراتی را که لازم است در Webpack اجرا کنیم مشاهده میکنید.
Babel loader
میدانیم که قاعده Webpack باید تغییر یابد. همان طور که به خاطر دارید این قاعده تنها امکان ارسال فایلهایی را به Babel میدهد که نام آنها به js. ختم میشوند. اینک regex را طوری تغییر میدهیم که .jsx را نیز بپذیرد. jsx. فایلهایی هستند که کامپوننتهای React ما را رندر میکنند:
1{
2 test: /\.jsx?$/,
3 use: "babel-loader"
4},
عبارت ?x به این معنی است که به صورت اختیاری میتواند در یک مورد مطابقت حاضر شود. این بدان معنی است که قاعده فوق با js. و همچنین با jsx. مطابقت پیدا میکند.
در پایان باید کاری کنیم که Webpack بتواند همه فایلهای jsx. را بدون لزوم به تصریح پسوند برای ایمپورتها resolve کند.
Resolve کردن JSX
در پیکربندی Webpack مقادیر پیوند پیشفرض را به jsx. اضافه میکنیم:
1resolve: {
2 extensions: [".wasm", ".mjs", ".js", ".json", ".jsx"];
3},
اینک به جای کد زیر:
میتوانید بدون تصریح پسوند، ایمپورت را انجام دهید:
اکنون شاید بپرسید چرا کاری نمیکنیم که پسوندهای تصویر، استایل و مدیا نیز به صورت خودکار Resolve شوند؟
دیدگاه ما این است که اگر یک با یک asset سر و کار داریم، باید صراحتاً اعلام شود که یک asset است و این مسئله را میتوانیم از روی پسوند دریابیم. مسئله دیگر این است که از تصادم نام جلوگیری میکند. مثلاً اگر فایل استایل دارای همان نام کامپوننت باشد یا تصویرهایی هم نام، اما با پسوندهای مختلف مانند png. و .webp داشته باشیم این کار از بروز مشکل جلوگیری میکند.
حالت توسعه React
بسته توسعه React بسیار بزرگتر از بسته پروداکشن است. برای این که به ریاکت اعلام کنیم باید از کدام یک استفاده کند، باید با استفاده از متغیر NODE_ENV آن را در اختیار Node.js قرار دهیم.
از آنجا که Webpack دیگر از NODE_ENV استفاده نمیکند، میتوانیم از پارامتر mode استفاده کنیم تا این مقدار را در اختیار React قرار دهیم. در ادامه افزونه را در webpack.config.js قرار میدهیم:
1const { DefinePlugin } = require(“webpack”);
و آن را به بخش افزونهها اضافه میکنیم:
1new DefinePlugin({
2 "process.env": {
3 NODE_ENV: JSON.stringify(argv.mode)
4 }
5}),
اکنون میتوانیم از بیلدهای توسعه/پروداکشن ریاکت بر اساس نوع تعریف شده استفاده کنیم.
قالب HTML به کامپوننت
ما در سراسر این سری مقالات از یک قالب رشته برای ایجاد HTML استفاده کردهایم، اما اینک زمان آن رسیده است که آن را به یک کامپوننت React تبدیل کنیم.
ابتدا نام آن را از index.js به index.jsx تغییر میدهیم و محتوا را با استفاده از یک کامپوننت React جایگزین میکنیم:
1import React, { useState } from "react";
2import { render } from "react-dom";
3
4import andHisNameIs from "./assets/and-his-name-is.mp3";
5import johnCena from "./assets/unexpected.jpg";
6import "./style.scss";
7
8const audio = new Audio(andHisNameIs);
9
10const App = () => {
11 const [personState, setPersonState] = useState("?");
12 const wakeUp = () =>
13 import(/* webpackChunkName: "myAwesomeLazyModule", webpackPreload: true */ "./lazy-one").then(
14 mod => setPersonState(mod.default)
15 );
16 const lazyBtnStyle = {
17 margin: "10px auto",
18 display: "flex",
19 fontSize: "4rem"
20 };
21
22 return (
23 <div id="myMemes">
24 <h1>You can't expect...</h1>
25 <img src={johnCena} role="button" onClick={() => audio.play()} />
26 <button style={lazyBtnStyle} onClick={() => wakeUp()}>
27 {personState}
28 </button>
29 </div>
30 );
31};
32
33const wrapper = document.createElement("div");
34wrapper.setAttribute("id", "app");
35document.body.appendChild(wrapper);
36
37render(<App />, wrapper);
ما اکنون دقیقاً همان اپلیکیشن بخشهای قبلی را داریم، اما این بار با React ساخته شده است.
مشکل افزایش اندازه bundle
اکنون که کد Vendors را داریم، باید به موضوع دیگری بپردازیم که اندازه bundle اصلی و کپیهای احتمالی آن است. اگر دستور زیر را اجرا کنید، میبینید که قطعه بزرگی از اپلیکیشن را اساساً React تشکیل میدهد:
yarn analyse
خوشبختانه Webpack امکان مقداری بهینهسازی در اختیار ما قرار میدهد.
ما به طور معمول اپلیکیشنهای خود را به دو bundle تقسیم میکنیم که کدهای خودمان و کدهای شخص ثالث هستند. به این منظور یک پیکربندی به فایل webpack.config.js اضافه میکنیم:
1optimization: {
2 splitChunks: {
3 cacheGroups: {
4 commons: {
5 test: /[\\/]node_modules[\\/]/,
6 name: 'vendors',
7 chunks: 'all'
8 }
9 }
10 }
11 },
با اجرای مجدد دستور yarn analyse این بار بسته vendors.js را با هر چیزی که از node_modules درون آن آمده است میبینیم.
راهاندازی React Router
پس از آن که تلاش کردیم bundle ما بین کد منبع و vendors افراز شود، نوبت آن رسیده است که react-router را راهاندازی و نوعی مسیریابی در اپلیکیشن خود تنظیم کنیم.
قبل از هر چیز دستور زیر را اجرا کنید:
yarn add react-router-dom
در این زمان میتوانید history API را روی webpack-dev-servern در فایل package.json فعال کنید و اسکریپت start:dev را به صورت زیر تغییر دهید:
webpack-dev-server --mode=development --history-api-fallback
ایجاد مسیرها
4 ماژول با نوعی کامپوننت درون دایرکتوری modules/ ایجاد میکنیم:
هر صفحه تنها Page ${number} را تغییر میدهد و به صورت Page-${number}.jsx نامگذاری میکند. اکنون باید از شر محتوای موجود در فایل index.jsx رها شویم و آن را با مسیرهای خود جایگزین کنیم:
1import React, { Fragment } from "react";
2import { BrowserRouter, Route, Link } from "react-router-dom";
3import { render } from "react-dom";
4
5import Page1 from "./modules/Page-1";
6import Page2 from "./modules/Page-2";
7import Page3 from "./modules/Page-3";
8import Page4 from "./modules/Page-4";
9
10const App = () => (
11 <BrowserRouter>
12 <Fragment>
13 <Route path="/" exact component={() => <h1>Home Page</h1>} />
14 <Route path="/page-1" component={Page1} />
15 <Route path="/page-2" component={Page2} />
16 <Route path="/page-3" component={Page3} />
17 <Route path="/page-4" component={Page4} />
18
19 <ul>
20 {[1, 2, 3, 4].map(number => (
21 <li key={number}>
22 <Link to={`/page-${number}`}>Page {number}</Link>
23 </li>
24 ))}
25 </ul>
26 </Fragment>
27 </BrowserRouter>
28);
29
30const wrapper = document.createElement("div");
31wrapper.setAttribute("id", "app");
32document.body.appendChild(wrapper);
33
34render(<App />, wrapper);
بارگذاری کُند کامپوننتها روی مسیرها
در کد فوق نکته جدیدی وجود ندارد و صرفاً برخی مسیرهای استاتیک تعریف شده است. اما اکنون با استفاده از React.lazy و <Suspense> میتوانیم بارگذاری کامپوننت را به تأخیر بیندازیم. به این منظور کافی است از ایمپورت دینامیک که در بخش قبلی دیدیم استفاده کنیم و مسیرهای استاتیک را به مسیرهای کُند تبدیل کنیم. بنابراین کد ایمپورتهای ماژول را به صورت زیر تغییر میدهیم:
1import React, { Suspense, lazy } from "react";
2
3// ... other imports
4
5const lazyRoute = lazyModule => {
6 const LazyComponent = lazy(lazyModule);
7 return (
8 <Suspense fallback={<div>Loading ...</div>}>
9 <LazyComponent />
10 </Suspense>
11 );
12};
13
14const Page1 = () => lazyRoute(() => import("./modules/Page-1"));
15const Page2 = () => lazyRoute(() => import("./modules/Page-2"));
16const Page3 = () => lazyRoute(() => import("./modules/Page-3"));
17const Page4 = () => lazyRoute(() => import("./modules/Page-4"));
18
19// ... the App component goes after that :)
اگر زبانه Network را در مرورگر خود باز کنید، میتوانید ببینید که بارگذاری صفحه تنها پس از کلیک کردن لینک اجرا میشود. نکته قابل توجه دیگر این است که Webpack هر نوع ماژولی که به صورت دینامیک ایمپورت شده باشد را کَش میکند، بنابراین اگر روی لینکی که قبلاً بازدید شده کلیک کنید، هیچ درخواست شبکه مجدداً ارسال نمیشود.
پیشواکشی صفحههای مهم
فرض کنید صفحهای به نام Page-4 برای کسب و کار شما حائز اهمیت بالایی است و میخواهید زمانی که کاربر روی آن کلیک میکند، در بارگذاری آن تأخیری پیش نیاید. در این حالت از مطلبی که در بخش قبل آموختیم، یعنی پیشواکشی (prefetch) این صفحه استفاده میکنیم:
1const Page4 = () =>
2 lazyRoute(() =>
3 import(
4 /* webpackPrefetch: true, webpackChunkName: "importantModule" */
5 "./modules/Page-4"
6 )
7 );
در بخش <head> این صفحه چیزی مانند زیر مشاهده میکنید:
1<link rel="prefetch" as="script" href="importantModule.js" />
تحلیل نتایج
چنان که هنگام استفاده از yarn analyse میبینید، صفحهها به چهار بخش کوچک تقسیم شدهاند که یکی از آنها ماژول importantModule.js را پیشواکشی میکند. میتوانید این راهبرد را نه تنها برای مسیرها بلکه برای مسیرهای فرعی بسته به اندازهشان انتخاب کنید و انتخاب هوشمندانهای است. در نهایت یک وب اپلیکیشن آماده پروداکشن را داریم. به این منظور یک مسیر کاملاً طولانی پنج بخشی را در این سری مقالات طی کردیم. از این که تا انتهای این سری مقالات با ما همراه بودید، متشکریم.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای JavaScript (جاوا اسکریپت)
- مجموعه آموزشهای برنامهنویسی
- ساخت اپلیکیشن های مدرن با Webpack — به زبان ساده
- توسعه وب اپلیکیشن با جاوا اسکریپت و Webpack — راهنمای کاربردی
- آموزش جامع Webpack (بخش اول) — از صفر تا صد
==
استفاده زیادی کردم. خیلی ممنون که مقاله به این خوبی رو به اشتراک قرار دادید