ساخت کتابخانه React و انتشار آن در NPM — راهنمای گام به گام
اغلب ما در طی عمر کاری خود مشغول توسعه اپلیکیشن هستیم و ساخت کتابخانه React تا حدودی دور از دسترس به نظر میرسد. اما در عمل چنین نیست. انتشار یک کامپوننت ریاکت روی NPM بهطوری که توسعهدهندگان دیگر بتوانند از آن استفاده کنند، فرایند سادهای است و میتواند بسیار الهامبخش باشد. بدین ترتیب میتوانیم مهارتهای ریاکت خود را ارتقا ببخشیم و با جامعه توسعهدهندگان آن همگام شویم.
در این راهنما قصد داریم یک کامپوننت نمونه به شکل یک ویجت ساده مدیوم توسعه دهیم که روی NPM منتشر خواهد شد. این کامپوننت یک نام کاربری به عنوان prop میگیرد و سپس آخرین فعالیت کاربر را واکشی میکند. مقالات و نظرات کاربر نیز در یک modal نمایش مییابند.
میتوانید این ویجت را در وبسایت خود قرار دهید و مقالات مدیوم خود را به وسیله آن نمایش دهید. در این لینک (+) میتوانید دمویی از این ویجت را ببینید و با وارد کردن نام کاربری خود در وبسایت مدیوم با طرز کار آن آشنا شوید.
ساخت ویجت
به لطف اسکریپت create-react-library (+) اغلب مراحل ساخت خودکار شده است. در ادامه به صورت گام به گام یک کامپوننت ریاکت به نام <MediumProfile /> میسازیم.
ابتدا create-react-library را به صورت سراسری نصب کنید:
npm install -g create-react-library
کتابخانه را مقداردهی کنید:
create-react-library
از شما خواسته میشود که جزییات زیر را وارد کنید:
اکنون همه چیز پکیج شده و آماده به کار است.
- گام 1: پوشه به پروژه cd کرده و دستور npm start را اجرا کنید. بدین ترتیب ماژول /src مورد نظارت قرار گرفته و هر زمان که تغییری در آن ایجاد شود در dist/ مجدداً کامپایل میشود.
- گام 2: خط فرمان دوم را باز کنید به پوشه /example بروید و دستور npm start را اجرا کنید.
اکنون یک سرور زنده داریم که تغییرات را در src و example مورد نظارت قرار داده و به صورت آنی بارگذاری مجدد میشود. از این رو میتوانیم به سادگی کامپوننت خود را توسعه دهیم. مراحل کار به صورت زیر است:
دو فایل اصلی وجود دارد که با آنها کار خواهیم کرد:
- src/index.js
- example/src/App.js
فایل اول یعنی src/index.js کامپوننت اکسپورت شده ما است که از سوی توسعهدهندگان دیگر مورد استفاده قرار میگیرد. این فایل شامل یک کامپوننت است که prop-ها را از فرم میگیرد و نوعی نتیجه در اختیار کاربر قرار میدهد.
توجه داشته باشید که ما نوع prop-های دریافتی را بررسی میکنیم. برای نمونه به کاربر اجازه میدهیم که prop به نام size را ارسال کند تا اندازه دکمه مدیوم را تغییر دهد. این بدان معنی است که باید size: Proptypes.number را به پروتوتایپهای خود اضافه کنیم.
فایل دوم یعنی example/src/App.js یک اپلیکیشن نمونه است که کامپوننت ما را ایمپورت میکند. بدین ترتیب props کامپوننت ارائه شده و نتیجه به دست میآید. نتیجه در مرورگر به صورت زیر است:
اکنون که دو فایل را در اختیار داریم، باید شروع به ساختن کامپوننت src/index.js بکنیم. کامپوننت ما یک کلمه به کاربر نشان میدهد که میتواند روی آن کلیک کنید تا فعالیتش در وبسایت مدیوم واکشی شود.
زمانی که کاربر کلیک کرد یک Modal باز میشود و دادههای واکشی شده کاربر را نمایش میدهد. چند بررسی خطا و حالت بارگذاری اضافه میکنیم تا به یک کامپوننت کاملاً عملیاتی تبدیل شود. برای استفاده از پکیجهای اکسترنال مانند react-simple-modal (+) باید آنها را به فایل package.json هم به صورت peerDependencies و هم به صورت devDependencies اضافه کنیم. وابستگیهای زیر را به فایل اصلی package.json اضافه کنید:
"react-fade-in": "^0.1.6", "simple-react-modal": "^0.5.1"
اکنون کافی است دستور yarn install را از دایرکتوری اصلی اجرا کنیم و همچنین دستور npm start را یک بار از دایرکتوری اصلی و بار دیگر از دایرکتوری example اجرا کنیم. اکنون میتوانیم از پکیجهای react-fade-in و simple-react-modal در کامپوننت خود استفاده کنیم و زمانی که توسعهدهندگان پکیج ما را نصب کردند، peerDependencies مرتبط با آن را نیز نصب کنند. اینک میتوانیم در عمل کامپوننت خود را بسازیم.
ما یک API ساختیم که فید RSS کاربر مدیوم را واکشی میکند و روی Heroku میزبانی شده است. با توجه به حیطه موضوعی این مقاله از شما میخواهیم که صرفاً این کامپوننت را در مسیر src/index.js کپی کنید، زیرا میخواهیم بر روی انتشار کامپوننت روی NPM متمرکز باشیم. کد زیر را در فایل src/index.js وارد کنید و همه محتوای قبلی را جایگزین کنید:
1import React, { Component } from "react";
2import PropTypes from "prop-types";
3import MediumLogo from "./medium.png";
4import Modal, { closeStyle } from "simple-react-modal";
5import FadeIn from "react-fade-in";
6
7export default class MediumProfile extends Component {
8 constructor(props) {
9 super(props);
10 this.state = {
11 loading: true,
12 title: undefined,
13 image: undefined,
14 items: undefined,
15 error: false,
16 show: false
17 };
18 this.open = this.open.bind(this);
19 }
20 static defaultProps = {
21 username: "dee.aye.en",
22 size: 100
23 };
24 static propTypes = {
25 username: PropTypes.string,
26 size: PropTypes.number
27 };
28 open() {
29 this.setState({ show: true, loading: true });
30 fetch(
31 `https://medium-profile-fetch.herokuapp.com/profile/@${this.props.username}`
32 )
33 .then(result => result.json())
34 .then(data => {
35 if (data.status === "ok") {
36 this.setState({
37 items: data.items,
38 title: data.feed.title,
39 image: data.feed.image,
40 loading: false
41 });
42 } else {
43 this.setState({
44 items: [],
45 image: MediumLogo,
46 error: true,
47 loading: false
48 });
49 }
50 });
51 }
52 render() {
53 return (
54 <div
55 style={{
56 width: `${this.props.size}px`,
57 height: `${this.props.size}px`,
58 cursor: "pointer",
59 boxShadow:
60 "0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)"
61 }}
62 >
63 <img
64 onClick={() => this.open()}
65 style={{ width: "100%", height: "100%" }}
66 src={MediumLogo}
67 />
68 <Modal
69 style={{ backgroundColor: "rgba(0,0,0,0.6)" }}
70 containerStyle={{
71 border: "2px solid black",
72 padding: "15px",
73 width: "70vw",
74 cursor: "auto",
75 boxShadow:
76 "0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)"
77 }}
78 closeOnOuterClick={true}
79 show={this.state.show}
80 onClose={() => this.setState({ show: false })}
81 >
82 <a
83 style={{ ...closeStyle, color: "white", backgroundColor: "black" }}
84 onClick={() => this.setState({ show: false })}
85 >
86 X
87 </a>
88 {!this.state.loading ? (
89 <div
90 style={{
91 display: "flex",
92 alignItems: "center",
93 justifyContent: "center",
94 flexDirection: "column"
95 }}
96 >
97 <img
98 src={this.state.image}
99 width="80px"
100 style={{
101 borderRadius: 40,
102 marginTop: "5px",
103 marginBottom: "15px",
104 boxShadow:
105 "0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)"
106 }}
107 />
108 <div
109 style={{
110 borderBottom: "1px solid lightgray",
111 marginBottom: "10px",
112 paddingBottom: "20px",
113 display: "flex",
114 alignItems: "center"
115 }}
116 >
117 {!this.state.error ? (
118 <h5
119 style={{
120 margin: 0,
121 padding: 0,
122 fontWeight: "lighter"
123 }}
124 >
125 Latest activity by{" "}
126 <a
127 style={{ fontWeight: "bolder", color: "black" }}
128 href={`https://medium.com/@${this.props.username}`}
129 target="_blank"
130 rel="noopener noreferrer"
131 >
132 {this.state.items.length > 0
133 ? this.state.items[0].author
134 : this.props.username}
135 </a>{" "}
136 on
137 </h5>
138 ) : (
139 <h5
140 style={{
141 margin: 0,
142 padding: 0,
143 fontWeight: "lighter"
144 }}
145 >
146 User not found
147 </h5>
148 )}
149 <img
150 style={{ marginLeft: "5px" }}
151 src={MediumLogo}
152 width="20px"
153 />
154 </div>
155 <FadeIn>
156 {this.state.items.map((item, index) => (
157 <div
158 key={index}
159 style={{
160 display: "flex",
161 alignItems: "center",
162 padding: "10px"
163 //width: "90%"
164 }}
165 >
166 <img
167 style={{
168 //border: "1px solid black",
169 marginRight: "20px",
170 boxShadow:
171 "0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)"
172 }}
173 src={
174 item.content[2] !== "p" ? item.thumbnail : MediumLogo
175 }
176 width="80px"
177 />
178 <div
179 style={{
180 margin: 0,
181 padding: 0,
182 color: "gray",
183 fontWeight: "lighter"
184 }}
185 >
186 <a
187 style={{ color: "gray" }}
188 target="_blank"
189 rel="noopener noreferrer"
190 href={item.link}
191 >
192 {item.title}
193 </a>
194 </div>
195 </div>
196 ))}
197 </FadeIn>
198 </div>
199 ) : (
200 <div>
201 <FadeIn>
202 <h3
203 style={{
204 margin: 0,
205 padding: 0,
206 textAlign: "center",
207 color: "lightgray",
208 margin: "100px"
209 }}
210 >
211 Fetching {this.props.username.toUpperCase()}...
212 </h3>
213 </FadeIn>
214 </div>
215 )}
216 </Modal>
217 </div>
218 );
219 }
220}
دکمه یک تصویر (medium.png) است و از این رو باید این تصویر را از این آدرس (+) دانلود کنید و در پوشه src/ قرار دهید. اکنون به آدرس example/src/App.js بروید و محتوای فایل را با کد زیر عوض کنید:
1import React, { Component } from "react";
2import MediumProfile from "react-medium-profile";
3
4export default class App extends Component {
5 constructor(props) {
6 super(props);
7 this.state = {
8 username: "dee.aye.en",
9 search: ""
10 };
11 }
12 render() {
13 return (
14 <div
15 style={{
16 display: "flex",
17 flexDirection: "column",
18 justifyContent: "center",
19 alignItems: "center",
20 padding: "70px"
21 }}
22 >
23 <h2 style={{}}>{"<MediumProfile />"}</h2>
24 <h5 style={{ color: "gray", marginBottom: "50px", marginTop: 0 }}>
25 npm i react-medium-profile
26 </h5>
27
28 <MediumProfile
29 username={
30 this.state.search.length > 0
31 ? this.state.search
32 : this.state.username
33 }
34 />
35 <h1 style={{ margin: 0, paddingTop: "30px" }}>
36 <span role="img" aria-label="pointing up">
37 ?
38 </span>
39 </h1>
40 <h5
41 style={{
42 textAlign: "center",
43 margin: 0,
44 paddingTop: "5px"
45 }}
46 onClick={() => this.setState({ username: "treyhuffine" })}
47 >
48 Click to fetch Medium profile
49 </h5>
50 <h6 style={{ margin: 0, paddingTop: "10px", color: "gray" }}>or</h6>
51 <input
52 style={{
53 marginTop: "10px",
54 border: "none",
55 marginBottom: this.state.search.length > 0 ? 0 : "40px",
56 padding: "3px",
57 textAlign: "center",
58 borderBottom: "1px solid black"
59 }}
60 type="text"
61 value={this.state.search}
62 onChange={e => this.setState({ search: e.target.value })}
63 placeholder="Find a profile"
64 ></input>
65 {this.state.search.length > 0 && (
66 <h6
67 style={{
68 marginTop: 0,
69 marginBottom: "40px",
70 paddingTop: "5px",
71 color: "gray"
72 }}
73 >
74 Now click the Medium button
75 </h6>
76 )}
77 <div
78 style={{
79 borderTop: "0.2px solid lightgray",
80 borderBottom: "0.2px solid lightgray",
81 width: "100%"
82 }}
83 >
84 <h4>props</h4>
85 <div style={{ marginLeft: "15px" }}>
86 <h4>
87 username: string <span style={{ color: "gray" }}>required</span>
88 </h4>
89 <h4>
90 size: number <span style={{ color: "gray" }}>optional</span>
91 </h4>
92 </div>
93 </div>
94 </div>
95 );
96 }
97}
ما صرفاً کامپوننت MediumProfile را از react-medium-profile ایمپورت کردهایم و آن را به صورت یک Prop به نام username ارائه میکنیم که میتواند از سوی پروفایل واکشی شود.
انتشار روی NPM
اینک به سادهترین بخش کار میرسیم. اسکریپت create-react-library به صورت خودکار یک دموی GitHub pages از پوشه examples ایجاد میکند و با استفاده از README به عنوان توضیح روی NPM منتشر میکند. مطمئن شوید که ابتدا با استفاده از دستور npm login وارد حساب NPM شدهاید و سپس دستورهای زیر را اجرا کنید:
npm publish
این دستور به صورت خودکار پکیج شما را روی NPM انتشار میدهد.
npm run deploy
این دستور به صورت خودکار مثال را روی GitHub pages توزیع میکند. برای مشاهده کد کامل این پروژه به این ریپوی گیتهاب (+) مراجعه کنید.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای JavaScript (جاوا اسکریپت)
- مجموعه آموزشهای برنامهنویسی
- آموزش جاوا اسکریپت (JavaScript)
- آموزش Node.js: آشنایی با npm و npx — بخش پنجم
- وابستگی همتا (Peer Dependencies) در npm — به زبان ساده
==