Category: Frontend
-
Upload file dung lượng lớn tới S3 với Multipart và Presign-url
Nếu như bình thường ta thực hiện single upload tới s3 thì sẽ có 2 cách sau:
-
Upload file lên s3 qua server của chúng ta: Cái này thì reject vì chúng ta phải tốn thời gian upload lên server của mình rồi từ đó mới upload lên S3. Với large file thì gần như là không nên.
-
Upload từ client bằng cách sử dụng presign-url. Thay vì phải qua server trung gian thì chúng ta upload thẳng lên S3. Tốc độ cải thiện rất nhiều vì cách 1 đa phần thời gian tốn ở khâu upload lên server của chúng ta.
Nhưng nói gì thì nói single upload to S3 thì sẽ có những hạn chế sau dưới Maximum size chỉ là 5GB . Điều này thực sự hạn chế. Hơn nữa AWS cũng suggest chúng ta là file >100MB thì nên sử dụng Multipart Upload . Vậy ưu điểm chính của nó là:
- Maximum size là 5TB
- Tốc độ upload tốt hơn
Điều đó mình lựa chọn multipart và presign-url cho bài toán upload file dung lượng lớn. Server ở đây mình sử dụng là python. Client thì dùng angular.
Server
Phần server này mình sử dụng kiến trúc serverless để triển khai với ngôn ngữ Python Flask trên môi trường AWS Cloud
Các bạn tham khảo project trên Github của mình để hiểu cách setup nhé.
Phần Server chủ yếu sẽ có 3 api chính:
-
Đầu tiên là api start-upload, với logic là request tới s3 để lấy uploadId
@app.route("/start-upload", methods=["GET"]) def start_upload(): file_name = request.args.get('file_name') response = s3.create_multipart_upload( Bucket=BUCKET_NAME, Key=file_name ) return jsonify({ 'upload_id': response['UploadId'] })
-
Tiếp theo là api get-upload-url để lấy presignurl cho từng part của file khi upload
@app.route("/get-upload-url", methods=["GET"]) def get_upload_url(): file_name = request.args.get('file_name') upload_id = request.args.get('upload_id') part_no = request.args.get('part_no') signed_url = s3.generate_presigned_url( ClientMethod ='upload_part', Params = { 'Bucket': BUCKET_NAME, 'Key': file_name, 'UploadId': upload_id, 'PartNumber': int(part_no) } ) return jsonify({ 'upload_signed_url': signed_url })
-
Cuối cùng đây là api để kiểm tra xem việc upload đã hoàn thành chưa.
@app.route("/complete-upload", methods=["POST"]) def complete_upload(): file_name = request.json.get('file_name') upload_id = request.json.get('upload_id') print(request.json) parts = request.json.get('parts') response = s3.complete_multipart_upload( Bucket = BUCKET_NAME, Key = file_name, MultipartUpload = {'Parts': parts}, UploadId= upload_id ) return jsonify({ 'data': response })
Client
Phần client này mình dùng Angular để triển khai 1 trang web upload file đơn giản.
Các bạn tham khảo project trên Github của mình để hiểu cách setup nhé.
Khi có action upload đầu tiên ta sẽ gọi tới function
uploadMultipartFile
. FunctionuploadMultipartFile
có chức năng với các step sau:-
Call api start-upload để lấy được uploadId.
const uploadStartResponse = await this.startUpload({ fileName: file.name, fileType: file.type });
-
Split file upload thành các chunks, ở đây chia thành 10MB/chunk nhé. Ở đây mình chia là 10MB vì minimum size của Multipart Upload là 10MB.
-
Thực hiện call api get-upload-url để lấy preSignurl cho từng chunk ở trên.
-
Upload các chunks bằng signurl.
-
Khi tất cả các chunks upload xong sẽ thực hiện call api complete-upload để xác nhận với S3 mình đã upload đầy đủ các part. Done.
try { const FILE_CHUNK_SIZE = 10000000; // 10MB const fileSize = file.size; const NUM_CHUNKS = Math.floor(fileSize / FILE_CHUNK_SIZE) + 1; let start, end, blob; let uploadPartsArray = []; let countParts = 0; let orderData = []; for (let index = 1; index < NUM_CHUNKS + 1; index++) { start = (index - 1) * FILE_CHUNK_SIZE; end = (index) * FILE_CHUNK_SIZE; blob = (index < NUM_CHUNKS) ? file.slice(start, end) : file.slice(start); // (1) Generate presigned URL for each part const uploadUrlPresigned = await this.getPresignUrl({ fileName: file.name, fileType: file.type, partNo: index.toString(), uploadId: uploadStartResponse.upload_id }); // (2) Puts each file part into the storage server orderData.push({ presignedUrl: uploadUrlPresigned.upload_signed_url, index: index }); const req = new HttpRequest('PUT', uploadUrlPresigned.upload_signed_url, blob, { reportProgress: true }); this.httpClient .request(req) .subscribe((event: HttpEvent<any>) => { switch (event.type) { case HttpEventType.UploadProgress: const percentDone = Math.round(100 * event.loaded / FILE_CHUNK_SIZE); this.uploadProgress$.emit({ progress: file.size < FILE_CHUNK_SIZE ? 100 : percentDone, token: tokenEmit }); break; case HttpEventType.Response: console.log('Done!'); } // (3) Calls the CompleteMultipartUpload endpoint in the backend server if (event instanceof HttpResponse) { const currentPresigned = orderData.find(item => item.presignedUrl === event.url); countParts++; uploadPartsArray.push({ ETag: event.headers.get('ETag').replace(/[|&;$%@"<>()+,]/g, ''), PartNumber: currentPresigned.index }); if (uploadPartsArray.length === NUM_CHUNKS) { console.log(file.name) console.log(uploadPartsArray) console.log(uploadStartResponse.upload_id) this.httpClient.post(`${this.url}/complete-upload`, { file_name: encodeURIComponent(file.name), parts: uploadPartsArray.sort((a, b) => { return a.PartNumber - b.PartNumber; }), upload_id: uploadStartResponse.upload_id }).toPromise() .then(res => { this.finishedProgress$.emit({ data: res }); }); } } }); } } catch (e) { console.log('error: ', e); }
Có những chú ý sau.
- Minimum size của Multipart Upload là 10MB. Maximum là 5TB
- Được upload tối đã 10000 chunks cho 1 file thôi. Ví dụ ở đây mình chia 1 chunk là 10MB thì mình chỉ upload tối đa 200GB. Tương tự 1 chunk là 5GB => upload tối đa là 5TB.
Demo
-
Cài đặt server với serverless. Vào project serverless và gõ lệnh
sls deploy
Có những chú ý sau:
-
Sau khi cài đặt NodeJS thì cài Serverless framework
npm i -g serverless
-
Cần có account AWS
-
Config ở file serverless đang sử dụng aws profile mà mình đã setup ở local(khanhcd92). Nếu bạn dùng default hoặc cấu hình profile riêng thì hãy thay đổi nó nhé. Cách cấu hình aws profile.
provider: name: aws runtime: python3.6 stage: dev region: ap-southeast-1 profile: khanhcd92
-
-
Thay đường dẫn API endpoint từ output của cài đặt server cho url trong class UploadService của project web
-
Cài đặt thư viện trong project web
npm i
-
Chạy web angular ở local với lệnh
npm start
-
Kéo file cần upload vào trình duyệt. Chỗ này mình sẽ dùng 1 file có dụng lượng khoảng 75M để upload.
-
Kiểm tra kết quả trên S3
Chi tiết source code các bạn xem ở đây nhé.
-
-
Triển khai angular web với AWS CDK
Ở phần đầu của series, mình đã giới thiệu AWS CDK là gì. Nay để hiểu rõ nó hơn mình sẽ giới thiệu cách triển khai 1 web angular với AWS CDK.
Chuẩn bị
- Chuẩn bị sẵn account AWS
- Cài đặt NodeJS
- Cài đặt Typescript:
npm i -g typescript
- Cài đặt AWS CLI
- Cấu hình AWS client:
aws configure
. Bạn sẽ cần nhập thông tin access key ID, secret access key và aws region. Chi tiết cấu hình bạn có tìm ở đây - Cài đẳt AWS CDK:
npm install -g aws-cdk
Thực hiện
-
Tạo một thư mục static_web_cdk.
-
Trong thư mục này tạo 2 thư mục con, 1 cái angular-sample-web cho website và 1 cái infrastructure cho phần infra.
Build source code Angular
- Di chuyển đến thư mục ngular-sample-web. Trong thư mục này mình sẽ download sample từ trên trang chủ Angular về để sử dung Xem chi tiết ở đây.
-
Di chuyển đến thư mục infrastructure
-
Khơi tạo source code infrastructure:
cdk init app --language typescript
. Lệnh này sẽ gen ra sample code cho chúng ta.
- Mở file bin/infrastructure.ts, thêm stackName và uncomment phần env. Phần env này chúng sẽ sử dụng mặc định như bên dưới.
#!/usr/bin/env node import 'source-map-support/register'; import * as cdk from '@aws-cdk/core'; import { InfrastructureStack } from '../lib/infrastructure-stack'; const app = new cdk.App(); new InfrastructureStack(app, 'InfrastructureStack', { /* If you don't specify 'env', this stack will be environment-agnostic. * Account/Region-dependent features and context lookups will not work, * but a single synthesized template can be deployed anywhere. */ stackName: 'web-static-stack', /* Uncomment the next line to specialize this stack for the AWS Account * and Region that are implied by the current CLI configuration. */ env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ });
- Bây giờ mở file
/lib/infrastructure-stack.ts
nơi chúng ta sẽ khai báo các dịch vụ của stack.
Khai báo các thành phần của Stack
Để triển khai Angular web static mình sử dụng các dịch vụ CloudFront và S3. CloudFront sẽ access vào private S3 thông qua OAI (Origin Access Identity). Vậy thì Stack cần phải code những gì mọi người theo dõi bên dưới nhé.
- Cài đặt thư viện
@aws-cdk/aws-s3
để sử dụng được dịch vụ s3. - Cài đặt thư viện
@aws-cdk/aws-cloudfront
để sử dụng dịch vụ cloudfront. - Cài đặt thư viện
@aws-cdk/aws-iam
để sử dụng khai báo Policy cho S3 - Trong file
lib/infrastructure-stack.ts
, chúng ta sẽ cần import các thư viện phía trên vào. Sau đó ở sau ta sẽ cần khai báo các dịch vụ bên duới dòng comment //The code that defines your stack goes here:
import * as cdk from '@aws-cdk/core'; import { BlockPublicAccess, Bucket, BucketEncryption } from '@aws-cdk/aws-s3'; import { CloudFrontWebDistribution, OriginAccessIdentity } from '@aws-cdk/aws-cloudfront'; import { PolicyStatement } from '@aws-cdk/aws-iam'; export class InfrastructureStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // The code that defines your stack goes here // Khai báo S3 const myBucket = new Bucket(this, "static-website-bucket", { bucketName: 'static-web-sample-s3', versioned: true, encryption: BucketEncryption.S3_MANAGED, blockPublicAccess: BlockPublicAccess.BLOCK_ALL, websiteIndexDocument: "index.html" }); // Khai báo OAI const oia = new OriginAccessIdentity(this, 'OIA', { comment: "Created by CDK" }); // Khai báo Policy cho S3 const policyStatement = new PolicyStatement(); policyStatement.addActions('s3:GetObject'); policyStatement.addResources(`${myBucket.bucketArn}/*`); policyStatement.addCanonicalUserPrincipal(oia.cloudFrontOriginAccessIdentityS3CanonicalUserId); myBucket.addToResourcePolicy(policyStatement); // Khai báo CloudFront new CloudFrontWebDistribution(this, 'static-website-cf', { originConfigs: [ { s3OriginSource: { s3BucketSource: myBucket, originAccessIdentity: oia }, behaviors: [ { isDefaultBehavior: true } ] } ] }); // Output giá trị DNS của CloudFront new CfnOutput(this, 'CloudFrontDNS', { value: cfWebStatic.distributionDomainName }) } }
- Tiêp theo chúng ta sẽ phần khai báo deploy. Bạn sẽ cần cài thư viện
@aws-cdk/aws-s3-deployment
và thêmimport * as s3Deployment from '@aws-cdk/aws-s3-deployment'
vào file stack và khai báo deploy như bên dưới:
const deployment = new s3Deployment.BucketDeployment(this, "deployStaticWebsite", { sources: [s3Deployment.Source.asset("../angular-sample-web/dist")], destinationBucket: myBucket, });
- Thuộc tính
s3Deployment.Source.asset("../angular-sample-web/dist")
là nơi mọi người sẽ cần update tới đúng vị trị source code mình đang dùng.
Triển khai
Bây giờ chúng ta sẽ cần triển khai stack này tới AWS:
-
Di chuyển đến thư mục angular-sample-web
-
Cài đặt các thư viện
npm i
-
Build Angular app
ng build --prod
. Lúc này bundle source code sẽ được gen ra trong thư mục dist -
Sau khi build xong Angular app thì di chuyển đến thư mục infrastructure
-
Trên cửa sổ command gõ lệnh:
cdk boostrap
. Thư mục cdk.out sẽ được tạo ra. Nó sẽ bao gồm code phần website và infrastructure template.
The first time you deploy an AWS CDK app into an environment (account/region), you’ll need to install a “bootstrap stack”. This stack includes resources that are needed for the toolkit’s operation. For example, the stack includes an S3 bucket that is used to store templates and assets during the deployment process.
- Chạy lệnh
cdk synth
. Nó sẽ tổng hợp lại CloudFormation template cho stack. - Chạy lệnh
cdk deploy
- Nhập y để tiếp tục triển khai
- Sau khi đã hoàn thành, chúng ta tới console của dịch vụ CloudFormation. Chúng ta sẽ thấy có 2 stack được tạo ra là CDKToolkit và web-static-stack
- Bây giờ vào tiếp stack web-static-stack để lấy link web.
- Click vào link CloudFrontDNS trên tab Output của màn hình stack. Bạn sẽ thấy kết quả như bên dưới.
NOTE:
-
Khi bạn không dùng nữa thì có thể chạy lệnh
cdk destroy
để xoá stack. -
Ví dụ bên trên là dùng cài đặt AWS client với giá trị default. Khi bạn đã thiết lập profile cho AWS thì tham khảo các câu lệnh để chạy cdk bên dưới đây nhé.
cdk boostrap --profile profile_name cdk synth --profile profile_name cdk deploy --profile profile_name cdk destroy --profile profile_name
Bạn có thể tham khao code ở Github
Tôi hy vọng nó sẽ có ích. Bạn cũng có thể thay thế Angular với ReactJS hoặc VueJS. Xin vui lòng cho tôi biết phản hồi của bạn.
-
ANT DESIGN – Điều gì khiến thư viện này vượt “mặt” Material-UI của Google?
Ant Design – Điều gì khiến thư viện này vượt "mặt" Material-UI của Google?
Nếu bạn là một Front-End developer và thường xuyên sử dụng những thư viện UI cho các dự án của mình thì chắc chắn các bạn đều sẽ biết đến những cái tên rất quen thuộc như Material-UI (MU) do Google phát triển hay Bootstrap do hai kỹ sư tại Twitter làm ra, tất cả đều là các thư viện hỗ trợ thật sự tuyệt vời về tính Responsive cho các màn hình thiết bị khác nhau, các components Javascript có sẵn.
Tuy nhiên, bạn biết đó, thời nào cũng sẽ có những anh hùng hào kiệt, một cái tên khác đang phất lên rất nhanh và cũng nhanh chóng được yêu thích của cộng đồng developers React Front-End đó là ANT DESIGN (ANTD) đạt 73,1k sao trên github và đang vượt lên so với Material-UI (70k sao). Có lẽ điều đầu tiên bạn sẽ biết về ANTD đó là thế lực tạo ra nó – Alibaba.
Vậy ANTD có gì mà có thể vượt lên so với MU – một thư viện UI/UX do Google phát triển rất lâu trước đó?
Tổng quan bài viết
ANT DESIGN và ANT DESIGN PRO
Bạn có phải lần đầu biết đến ANTD? Có khoảnh khắc nào trong những giây phút "chilling" bạn search google với keyword "ant design là gì?" nhưng chắc có lẽ bạn vẫn chưa biết tới cái tên ANT DESIGN PRO nếu chưa bao giờ sử dụng ANTD.
ANT DESIGN và ANT DESIGN PRO là hai phần khác nhau của ANTD.
ANT DESIGN: Dành cho những components đã được viết sẵn, bạn chỉ cần import và tái sử dụng lại những components đó, ví dụ như Carousels, Form, Upload, Modal, Badge,…etc.
- Điều khiến ANTD trở nên đặc biệt là Document cực kì rõ ràng, các api của components đều có demo và ví dụ đi kèm, bạn có thể xem code trong codeSandbox/codepen/stackblitz. Một số components được ANTD viết với TypeScript, cùng nghía qua một "tí tẹo" document của ANTD nha:
Nếu những điều này chưa khiến bạn phải "quaoooo" thì hãy xem tới các props của ANTD và khả năng hỗ trợ Customize Base component của nó.
Bạn biết đấy, đôi khi làm việc với các thư viện, bạn sẽ muốn thay đổi các base components để đáp ứng như cầu của khách hàng/dự án và props của ANTD sẽ là công cụ đắc lực giúp bạn dễ customize hơn và viết code tối giản hơn nhiều.
Vậy còn ANTD PRO thì sao?
ANT DESIGN PRO thì khác, nó là một theme gần như hoàn chỉnh dành cho các trang Admin, CMS ( Control Management System). Bạn có thể thấy được những gì mà ANTD PRO có thể hỗ trợ bạn qua <a href="https://preview.pro.ant.design/" target="_blank">website preview</a>.
- Cài đặt ANTD PRO với npm hoặc yarn:
Sau khi cài đặt, bạn sẽ thấy rằng điều bạn cần làm là chọn những phần components bạn muốn và bắt đầu gọi API từ phía Back-end hoặc gỉa lập API với file
_mock.js
có sẵn của ANTD PRO luôn mà không cần phải đau đầu suy nghĩ về UI hay viết code làm sao. Rất tiện lợi đúng không?Chưa hết, ANTD PRO còn mang đậm tính "all-in-one", chỉ 1 lần cài đặt mà bạn có thể sử dụng luôn:
-
Webpack (Webpack giúp tăng tốc độ cho dự án, đóng gói thành một file duy nhất,…)
-
Less ( một CSS preprocessor giống với SCSS )
và
-
Sự tuyệt vời đến từ UmiJS – cái tên bạn … chưa nghe qua bao giờ đúng không?
UmiJS: chính là một thư viện Plugin enterprise-level cho React. Nghe thì xa tầm với nhưng ý lại nằm trong lời, UmiJS bao hàm bên trong là redux-saga, gọi api thay vì với Axios thì sẽ là Request.
Ai cũng biết Axios hỗ trợ việc gọi api request tốt và "uy tín" như thế nào nhưng UMI-Request lại hơn thế đấy, cùng xem bảng so sánh của umi-request với Axios và Fetch sau:
Bạn đã bị thuyết phục chưa? Nếu chưa thì hãy tới mục số II nha.
WEB COMPONENT vs MOBILE COMPONENT
Giống như sự hỗ trợ của ANTD dành cho web version thì ANTD cũng đã dành điều đó cho cả <a href="https://mobile.ant.design/docs/react/introduce" target="_blank">mobile version</a>.
Version dành cho Mobile là cái mà Material UI chưa làm nhưng ANTD đã làm và còn đầu tư chất lượng cho document của mobile không kém cạnh gì cho web version.
=> Có thể thấy ANTD là một thư viện UI được đầu tư, phát triển mạnh mẽ và đến đây chúng ta cũng có thể hiểu tại sao số sao trên github dành cho ANTD lại vượt nhanh hơn cả MU.
Tips và ví dụ customize sử dụng PROPS của ANTD
Từ mục I và II chúng ta đều được biết tới sự hỗ trợ "xịn xò" cho các developers React Front-End nhưng lại không biết nhược điểm của nó là gì và tips để sử dụng ANTD hiệu quả đúng không?
Nhược điểm của ANTD:
- Document của ANTD vẫn còn một vài chỗ viết bằng tiếng Trung, đặc biệt là Document của ANTD PRO/UMIJS
=> Việc tiếng Trung còn nhiều khiến cho các developers gặp khó khăn khi muốn customize components hoặc đọc về API/Props hỗ trợ.
- Trên các trang như Stackoverflow, dev.to,… có ít các bài được giải thích hoặc hỗ trợ cho ANTD.
=> Vậy nên khi gặp bug, các bạn cũng có thể mất kha khá thời gian để sửa và kiểm tra lại.
- Tuỳ chỉnh Theme
=> Khả năng tuỳ chỉnh theme của ANTD không mạnh bằng MU khi chỉ có thể thay đổi các mặc định về màu sắc, kích thước,…
Tips khi sử dụng ANTD:
- Hãy đọc kĩ các phần API, props của ANTD hỗ trợ.
=> Có thể khi đứng trước một vấn đề mà vấn đề đó cần customize một base component của ANTD mà bạn chưa có ý tưởng bắt đầu làm như thế nào, hãy đọc kì api/props mà component đó có, có thể bạn sẽ nhận ra mình đỡ bỏ lỡ một sự kết hợp hay ho đó.
- Hãy viết style bằng
less
=> ANTD cũng không quá khắt khe cho việc viết style như khi dùng bootstrap nhưng để tối ưu việc style, responsive và sự tương thích với các trình duyệt khác nhau thì hãy viết với
less
nhé.- Ví dụ về customize sử dụng PROPS của ANTD
Customize dropdown của
<Select>
với infinity scrollCó 5 bước chúng ta cần làm (nguồn: <a href="https://codesandbox.io/s/antd-select-infinite-scroll-90shp?file=/index.js:1649-2578)" target="_blank">click here to open codeSandbox</a>)
Bước 1: Import các phần cần thiết và viết một component:
import React from "react"; import ReactDOM from "react-dom"; import "antd/dist/antd.css"; import "./index.css"; import Select from "antd/lib/select"; const { Option } = Select; class LazySelect2Input extends React.PureComponent { render() { return <div className="App"></div>; } }
Bước 2: Viết hàm để render mock data:
// Fetch at most 200 // On scroll uses a queue // Pop const children = [...Array(20).keys()]; const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + i * step); const handleChange = (value) => console.log(`selected ${value}`);
Bước 3: Khai báo constructor với props, state:
constructor(props) { super(props); this.state = { lastScrollPos: 0, loadingMore: false, options: children, min: 0, search: undefined }; }
Bước 4: Viết function để thực hiện infinity scroll:
handleScroll = (event) => { event.stopPropagation(); let delta; const { options, min, loadingMore } = this.state; let newMin = min; if (event.wheelDelta) { delta = event.wheelDelta; } else { delta = -1 * event.deltaY; } if (!loadingMore) { if (delta < 0) { newMin = min + 1; if (newMin + 10 >= Math.max(...options)) { // Fetch more items when the list is exhausted. this.handleFetchMore(Math.max(...options) + 1, 10, newMin); } } else if (delta > 0 && min >= 1) { newMin = min - 1; } return this.setState({ min: newMin }); } }; handleFetchMore = (offset, limit, min) => { this.setState( { loadingMore: true, options: [...this.state.options, ...range(offset, offset + limit, 1)], }, () => this.setState({ loadingMore: false, min, }) ); };
Bước 5: Render và Return:
render() { const { loadingMore, min, options, search } = this.state; let currentOptions = options; const extraProps = {}; if (search) { currentOptions = options.filter(v => v.toString().indexOf(search) >= 0); extraProps.open = true; extraProps.key = "searching"; } return ( <div className="App"> <Select mode="multiple" style={{ width: "100%" }} onChange={handleChange} // dropdownRender, filterOption, defaultActiveFirstOption // are props for customize of ANTD dropdownRender={menu => <div onWheel={this.handleScroll}>{menu}</div>} loading={loadingMore} onSearch={search => this.setState({ search })} filterOption={false} defaultActiveFirstOption={false} {...extraProps} > {currentOptions.slice(min, min + 10).map(i => ( <Option key={i} value={i}> {i} </Option> ))} </Select> </div> ); }
=> Có thể thấy các props dropdownRender, filterOption, defaultActiveFirstOption đã hỗ trợ cho chúng ta bắt sự kiện scroll khi tag Select xổ xuống các option để việc thực hiện tải thêm data khi người dùng cuộn xuống phần đáy của tag select (popup container).`
Tổng kết
Chúng ta đã có một bài đọc khá dài về ANT DESIGN. Hi vọng những gì mình chia sẻ có thể giúp bạn hiểu thêm về nó và có thể dễ dàng bắt đầu sử dụng hơn nha. Cảm ơn các bạn đã đọc.
-
Triển khai CD cho dự án phát triển Website với Gitlab-CI và AWS S3
Article overview
Giả sử chúng ta phát triển một sản phẩm Website với ReactJS và sử dụng Static Website Hosting của AWS S3. Mỗi lần deploy đều cần thực hiện build source và upload manual.
Mục tiêu là triển khai CD để thay thế cho các công việc manual không cần thiết và giảm thiểu các sai sót ngoài ý muốn.Tổng quan về các công nghệ sử dụng:
- ReactJS Website
- Gitlab-CI
- AWS S3, AWS CLI
- Môi trường MacOS
Table of contents
Chúng ta cần một số bước sau:
- Liên kết và khởi tạo Runner với Gitlab repository
- [Cài đặt và cấu hình môi trường tại thiết bị chạy service runner](## Cài đặt và cấu hình môi trường tại thiết bị chạy service runner)
- [Cấu hình các job CI/CD với .gitlab-ci.xml và Gitlab-CI](## Cấu hình các job CI/CD với gitlab-ci.xml và Gitlab-CI)
Cài đặt và cấu hình môi trường tại thiết bị chạy service runner
Giả định ở thiết bị MacOS chạy service runner đã build được Website ReactJS, chúng ta sẽ skip qua phần cài đặt cho ReactJS. Đầu tiên, chúng ta cần cài đặt AWS CLI.
Sau khi cài đặt xong, ta thực hiện config với thông tin của AWS User có quyền deploy lên AWS S3 với câu lệnh sau:$ aws configure AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY Default region name [None]: us-west-2 Default output format [None]: json
Cấu hình các job CI/CD với gitlab-ci.xml và Gitlab-CI
Đầu tiên, thay vì upload bằng tay các file trong thư mục build/ chúng ta sẽ sử dụng command aws s3 sync như sau:
aws s3 sync build/ s3://ww95
với ww95 là tên của Bucket đang Host Website.
Sau đó, chúng ta sẽ cài đặt command package.json như sau:"scripts": { "build": "react-scripts build", // Build source code để deploy "deploy": "aws s3 sync build/ s3://ww95" // Thực hiện deploy lên S3 },
Tiếp đó, ta sẽ cấu hình .gitlab-ci.yml để hệ thống tự động deploy khi có thay đổi trên nhánh master.
stages: - Deployment deploy: stage: Deployment before_script: [] only: - master allow_failure: true script: - yarn install - yarn build - yarn deploy
Tuy nhiên, khi chạy thực tế trên Gitlab-CI ta sẽ gặp lỗi sau:
Treating warnings as errors because process.env.CI = true. Most CI servers set it automatically. Failed to compile.
Để giải quyết vấn đề này ta phải setting lại
process.env.CI = false
bằng hai cách sau:- Thay đổi cấu hình .gitlab-ci.yml từ
yarn build
->CI=false yarn build
. - Cài đặt biến môi trường trong Gitlab-CI như ảnh sau
Sau đó merge code vào master, và hưởng thành quả. Từ giờ các bạn không cần phải deploy bằng tay nữa rồi, chúc các bạn may mắn.
Authors
MinhNN44
-
[React] Kết hợp các mẫu lập trình để sử dụng hiệu quả Runtime Type Checking
Như các bạn đã biết, Static Type Checking giúp chúng ta kiểm soát kiểu dữ liệu tốt hơn khi viết mã nguồn. Các bạn có thể sử dụng Flow hoặc TypeScript đều được. Tuy nhiên khi dữ liệu được liên kết với API chẳng hạn, khi đó bạn không thể kiểm soát được giá trị được truyền vào cho biến nào đó. Do đó chúng ta vẫn cần thêm một bước nữa để hạn chế các lỗi tiềm ẩn bằng Runtime Type Checking. Trong bài viết này tôi sẽ hướng dẫn các bạn sử dụng Runtime Type Checking như thế nào cho hiệu quả.
Runtime Type Checking là gì?
Runtime Type Checking là quá trình xác minh lại kiểu dữ liệu của giá trị truyền vào cho biến có đúng với kiểu dữ liệu mong muốn hay không. Trong ứng dụng React khi bạn khai báo
propTypes
cho component nào đó chính là lúc bạn đang định nghĩa kiểu dữ liệu mong muốn. Tại thời điểm thực thi các định nghĩa này sẽ được kiểm tra giúp bạn kiểm soát việc binding dữ liệu từ API vào component có tương thích với nhau hay không.Định nghĩa
propTypes
React cung cấp gói
prop-types
giúp bạn định nghĩa các thuộc tính cần cho Runtime Type Checking. Trước đây thì gói này nằm trong React, hiện tại thì nó đã tách ra thành một gói riêng rồi. Các bạn khai báopropTypes
như sau:import React from 'react'; import PropTypes from 'prop-types'; export function Octocat(props) { const { login, avatar_url, name } = props; return ( <ul> <li>{login}</li> <li>{name}</li> <li> <img src={avatar_url} alt={login} /> </li> </ul> ); } Octocat.propTypes = { login: PropTypes.string, avatar_url: PropTypes.string, name: PropTypes.string };
Trong quá trình thực thi nếu dữ liệu được truyền vào component không đúng với định nghĩa
propTypes
bạn sẽ nhận được một thông báo tương tự như sau:Kết hợp với Spread Attributes
Bạn có để ý rằng khi chúng ta khai báo giá trị truyền vào trong props thì khi sử dụng component này bạn phải khai báo như sau:
<Octocat login="abc" avatar_url="def" name="xyz" />
Nếu số lượng thuộc tính trong component này tăng lên thì việc viết mã nguồn sẽ khá là vất vả. Rất may là trong JSX cung cấp cho bạn tính năng Spead Attributes và bạn có thể viết code như sau:
const data = {login: "abc" avatar_url: "def" name: "xyz"}; <Octocat {...data} />
Toán tử
...
được gọi là toán tửspead
.Kết hợp với Destructuring Props
Các bạn có thấy rằng khi khai báo
Octocat
component tôi đã truyềnprops
như làm tham số của một hàm và sau đó mới gán lại vào các biến. Với Destructuring Props bạn có thể viết mã nguồn đơn giản hơn như sau:import React from 'react'; import PropTypes from 'prop-types'; export function Octocat({ login, avatar_url, name }) { return ( <ul> <li>{login}</li> <li>{name}</li> <li> <img src={avatar_url} alt={login} /> </li> </ul> ); } Octocat.propTypes = { login: PropTypes.string, avatar_url: PropTypes.string, name: PropTypes.string };
Tài liệu tham khảo:
Cám ơn các bạn đã theo dõi bài viết. Hy vọng bài viết sẽ giúp các bạn viết mã nguồn tốt hơn.
-
[React] Static Type Checking là gì? Flow và TypeScript, bạn chọn cách nào?
Static Type Checking và Runtime Type Checking là hai thuật ngữ ban đầu cảm giác có vẻ không quen thuộc cho nhưng nó lại lai thứ có lẽ các bạn đang dùng hàng ngày. Như các bạn biết thì JavaScript là ngôn ngữ định kiểu động, nghĩa là kiểu dữ liệu chỉ được xác định tại thời điểm thực thi. Do đó khi lập trình chúng ta không cần xác định kiểu dữ liệu cho biến. Điều này vô hình chung làm cho mã nguồn của chúng ta trở nên khó đọc hơn và khó debug hơn nữa. Chính vì vậy việc xác định kiểu dữ liệu giúp cho chúng ta kiểu soát và debug dễ dàng hơn. Trong bài viết này tôi sẽ chia sẻ với các bạn cách công đồng đang làm để hỗ trợ hai kiểu kiểm tra kiểu dữ liệu này.
Static Type Checking là gì?
Hiện nay có hai xu hướng để kiểm tra kiểu dữ liệu tĩnh là Flow và TypeScript. Static Type Checking sẽ phân tích mã nguồn tĩnh trước khi biên dịch mã nguồn sang mã JavsScript(ES5) là mã nguồn mà trình duyệt có thể đọc được. Vậy thì chúng khác nhau ở điểm nào?
Flow
Flow là một phần mở rộng của Babel cung cấp cho bạn việc kiểm tra kiểu dữ liệu tĩnh bằng cách sử dụng các chỉ thị bằng comment mà bạn thêm vào mã nguồn.
Ví dụ cách sử dụng flow như bên dưới:
// @flow function square(n: number): number { return n * n; } square('2'); // Error!
TypeScript
TypeScript là ngôn ngữ dựa trên JavaScript được Microsoft phát triển. TypeScript khác với Flow là nó không phân tích mã nguồn dự trên các chỉ thị bằng comment. Kiểu dữ liệu được hỗ trợ từ trong ngôn ngữ luôn. Dữ liệu được kiểm tra tại thời điểm phân tích cú pháp của chương trình dịch. Ngoài ra thì TypeScript hỗ trợ rất mạnh OOP nên nó rất phù hợp với các bạn quen thuộc với các ngôn ngữ định kiểu như Java và C#.
const add = (x: number, y: number) => { return x + y; };
Sử dụng Static Type Checking có lợi ích gì?
-
Khi sử dụng Static Type Checking bạn được hỗ trợ việc kiểm soát dữ liệu tại thời điểm viết mã nguồn, tất cả việc gán hoặc gọi hàm với kiểu dữ liệu không phù hợp sẽ được phát hiện tại thời điểm này.
-
IDE hỗ trợ việc kiểm soát kiểu dữ liệu giúp bạn viết code nhanh hơn và sai sốt ít hơn.
-
Mã nguồn dễ đọc hơn cho người mới.
Bạn mất gì khi sử dụng Static Type Checking?
Static Type Checking mang những lợi ích nhất định trong việc kiểm soát kiểu dữ liệu, tuy nhiên nó cũng có những nhiểm điểm:
-
Bạn phải viết code nhiều hơn thì mới kiểm soát được kiểu dữ liệu, việc này đương nhiên sẽ tốn công sức. Tuy nhiên so với việc mất thời gian vào debug thì có lẽ nó vẫn tốt hơn.
-
Bạn vẫn không kiểm soát được kiểu dữ liệu tại thời điểm chạy chương trình, lý do là nếu chương trình của bạn có liên kết với API thì không có gì đảm bảo kiểu dữ liệu của bạn chạy đúng với backend cả. Đương nhiên cái này là vấn đề không thể giải quyết được rồi. Tôi sẽ hướng dẫn các bạn giải quyết vấn đề này trong bài viết Runtime Type Checking sau nhé.
Kết luận
Đây chỉ là kết luận mang tính cá nhân của mình :). Các bạn tự suy ngẫm để đưa ra lựa chọn phù hợp nhé.
-
Việc viết theo Flow có cảm giác không tự nhiên lắm, thường xuyên phải thêm comment khiến bạn khá mất thời gian. Thêm một vấn đề nữa là lúc viết code sử dụng Flow khá tốn tài nguyên của máy. Về vấn đề này thì mình thấy TypeScript có vẻ ổn hơn.
-
TypeScript không đơn giản là một sự mở rộng của JavaScript, Microsoft đã thêm vào nhiều thứ hơn thế và nó cũng không dựa trên các tiêu chuẩn của ES6, ES7, … (có support ES6, ES7, … nhưng có những thêm vào nhiều thứ khác nữa). Về điểm này thì Flow có vẻ tốt hơn.
-
Sau sự ra đời của React Hooks thì mọi thế mạnh của TypeScript gần như không còn nữa so với Flow, bản thân mình cũng không dùng TypeScript nữa.
-
Một điểm quan trong nữa là cả TypeScript và Flow đều không thể xác mình kiểu dữ liệu lúc thực thi nên bạn vẫn cần Runtime Type Checking.
Các bạn có dùng Static Type Checking không? Mình thì đã không dùng nữa và chuyển sang xác mình toàn bộ bằng Runtime Type Checking rồi. Thời gian tốn cho debug cũng không phát sinh nhiều so với việc phải viết mã nguồn xác định kiểu dữ liệu.
Cảm ơn các bạn đã theo dõi bài viết. Các bạn cùng chờ bài viết mình hướng dẫn về Runtime Type Checking nhé.
-
-
[React] Sử dụng mẫu container trong React như thế nào?
Chắc hẳn ai cũng biết tới nguyên lý đầu tiên của SOLID đó là trách nhiệm đơn nhất. Nghĩa là mỗi một đối tượng chỉ làm một nhiệm vụ duy nhất. Đơn giản vì với tính đơn nhất chúng ta sẽ dễ dàng kiểm soát mã nguồn hơn (cho cả người code lần người review). Chưa kể tới việc với tính đơn nhất thì mã nguồn của bạn cũng trở nên trong sáng hơn. Với anh em lập trình React cũng vậy, bài toán khá đau đầu là làm sao tách logic ra khỏi phần mã nguồn kết xuất HTML. Cách làm như vậy giúp chúng ta thay vì viết một React Component để là cả hai việc thì bây giờ chúng ta sẽ viết hai React Component để làm hai nhiệm vụ khác nhau. Trong bài viết này tôi sẽ hướng dẫn các bạn sử dụng mấu container để làm việc đó.
Container component là gì?
Chúng ta không có một định nghĩa chính xác nào về container component cả, container component đơn giản là một React Component làm nhiệm vụ goi API để lấy dữ liệu và sau đó truyền nó xuống một React Component để kết xuất mã HTML.
Định nghĩa container
Để các bạn dễ hình dung tôi sẽ viết thử một container, container này sẽ thực hiện lấy thông tin của octocat
src/containers/OctocatContainer.jsx
import React, { useState, useEffect } from 'react'; import { Octocat } from '../components/Octocat'; export function OctocatContainer() { const [octocat, setOctocat] = useState(null); useEffect(() => { async function getOctocat() { // gọi hàm fetch với phướng thực mặc định là GET const response = await fetch('https://api.github.com/users/octocat'); const body = await response.json(); // sử dụng React Hook để thông tin octocat vào state setOctocat(body); } getOctocat(); }, []); // tham số mảng rỗng thể hiện hàm effect không phụ thuộc vào đối tượng nào, nó tương đương với hàm componentDidMount return <Octocat {...octocat} />; }
Trong ví dụ trên tôi có sử dụng thêm JSX spread attributes để tránh việc phải khai báo
props
cho Octocat component. Mẫu này đặc biệt có ý nghĩa đối với các component mà bạn cần khai báo nhiềuprops
.Định nghĩa component để kết xuất mã HTML
Sau khi có dữ liệu rồi thì chúng ta sẽ kết xuất nó ra HTML như sau:
src/components/Octocat/Octocat.jsx
import React from 'react'; export function Octocat(props) { const { login, avatar_url, name } = props; return ( <ul> <li>{login}</li> <li>{name}</li> <li> <img src={avatar_url} alt={login} /> </li> </ul> ); }
Như các bạn thấy, component trên chỉ làm nhiệm vụ bind dữ liệu lên màn hình mà thôi.
Cùng xem kết quả nhé 🙂
-
[React] Tôi đã làm toast component như thế nào?
Với bất kỳ ứng dụng nào việc hiển thị thông báo lỗi là không thể thiếu được. Các bạn có thể có rất nhiều cách làm để hiển thị được các thông báo lỗi này. Trong bài viết này tôi xin chia sẻ cách làm của tôi để các bạn cùng tham khảo nhé.
Với bất kỳ ứng dụng nào việc hiển thị thông báo lỗi là không thể thiếu được. Các bạn có thể có rất nhiều cách làm để hiển thị được các thông báo lỗi này. Trong bài viết này tôi xin chia sẻ cách làm của tôi để các bạn cùng tham khảo nhé.
Mục đích của tôi khi viết component này phải đảm bảo nó dễ dùng như hàm
window.alert()
vậy. Trong bài viết này tôi sẽ sử dụng Material UI để hiển thị thông báo lỗi.Toast Context
Vì chúng ta cần hàm hiển thị thông báo lỗi có thể sử dụng như
window.alert()
nên chúng ta cần khai báo một nơi lưu trữ để từ đó chúng ta có thể gọi được ở bất kỳ đâu trong ứng dụng. Có nhiều bạn sẽ nghĩ tới Redux nhưng trong bài viết này tôi sẽ sử dụng React Context API để làm việc đó. Chúng ta bắt đầu bằng việc khai báoToastContext
nào.src/contexts/toast.js
import React, { useContext } from 'react'; export const ToastContext = React.createContext(); export const useToast = () => useContext(ToastContext);
Toast Provider
Trong React Context API có định nghĩa Provider để khi có sử thay đổi trong component thì các component bên trong có thể nhận biết sử thay đổi đó và phản anh kết quả thay đổi lên màn hình. Ở đây chúng ta cần hiển thị message lỗi mỗi khi có component nào đó thông báo về việc hiển thị message lỗi.
import React, { useState } from 'react'; import { Snackbar } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import { ToastContext } from '../contexts/toast'; export const ToastProvider = (props) => { const { children } = props; const [state, setState] = useState({ isOpen: false }); const show = (message) => { setState({ isOpen: true, message }); }; const hide = () => setState({ isOpen: false }); const error = (message) => { show({ type: 'error', text: message }); }; const warn = (message) => { show({ type: 'warning', text: message }); }; const info = (message) => { show({ type: 'info', text: message }); }; const success = (message) => { show({ type: 'success', text: message }); }; const { isOpen, message } = state; return ( <ToastContext.Provider value={{ error: error, warn: warn, info: info, success: success, hide: hide }}> {children} {message && ( <Snackbar open={isOpen} autoHideDuration={6000} onClose={hide}> <Alert elevation={6} variant="filled" onClose={hide} severity={message.type}> {message.text} </Alert> </Snackbar> )} </ToastContext.Provider> ); };
Sử dụng các hàm
error
,warn
,info
,success
để hiển thị thông báoĐể sử dụng được các hàm này trong ứng dụng bạn cần khai báo nó trong component chính của ứng dụng
import React from 'react'; import { ToastProvider } from './providers/ToastProvider'; import { useToast } from './contexts/toast'; import './App.css'; function ButtonList() { const { error, warn, info, success } = useToast(); return ( <> <button onClick={() => error('error message!')}>error</button> <button onClick={() => warn('warn message!')}>warn</button> <button onClick={() => info('info message!')}>info</button> <button onClick={() => success('success message!')}>success</button> </> ); } function App() { return ( <ToastProvider> <ButtonList /> </ToastProvider> ); } export default App;
Cám ơn các bạn đã theo dõi bài viết. Hy vọng bài viết đã cung cấp cho các bạn thêm một các để hiển thị thông báo tốt hơn cho ứng dụng của bạn.
-
[React] Class Component và Function Component, bạn chọn viết theo cách nào?
Trong thế giới React thì chắc hẳn ai cũng biết đến Class Component và Function Component. Tuy nhiên có thể có những hiểu nhầm về hai loại component này. Trong bài viết này tôi sẽ thử so sánh hai cách viết này để giúp bạn có thể lựa chọn viết theo cách nào. Chúng ta cùng bắt đầu nhé.
Cú pháp
Khác nhau đầu tiên giữa
Class Component
vàFunction Component
thể hiện ngay ở cách khai báo.Class Component
import React, { Component } from 'react'; class TestComponent extends Components { // phương pháp này bắt buộc phải khai báo hàm để kết xuất mã HTML render() { return <div>TestComponent</div>; } }
Cách khai báo này khá quen thuộc với các bạn có nền tảng lập trình hướng đối tượng (OOP). Với những bạn mới học React hoặc chuyển sang học React thì phương pháp tiếp cận này có vẻ phù hợp và dễ hiểu.
Function Component
import React from 'react'; export function TestComponent() { // phương pháp xem kết xuất mã HTML như là giá trị trả về của hàm return <div>TestComponent</div>; }
Function component sử dụng cách tiếp cận khác đó là sử dụng pure function để khai báo component. Ban đầu function component được sử dụng để viết các component chỉ với mục đích kết xuất HTML mà thôi. Với các component với theo hướng tiếp cận này thì bạn sẽ không can thiệp được vào lifecycle của component. Do đó nó thướng được biết đến với tên gọi Stateless Component.
Props
Class Component
props
trong Class Component được xem như giá trị truyển vào cho hàm khởi tạo class.import React, { Component } from 'react'; class TestComponent extends Components { constructor(props) { super(props); // bắt buộc phải có dòng này để gọi hàm khởi tạo của class cha nhé } render() { return <div>TestComponent</div>; } }
Function Component
props
trong Function Component thì được xem như là giá trị truyền vào hàm pure function khi định nghĩa component.import React from 'react'; export function TestComponent(props) { return <div>TestComponent</div>; }
Định nghĩa
defaultProps
vàpropTypes
thì không có sự khác biệt giữa Class Component và Function Component.TestComponent.defaultProps = {}; TestComponent.propTypes = {};
State
Trước khi React Hooks ra đời thì như đã nói ở trên Function Component con được biết đến với tên gọi Stateless Component. Nghĩa là nó không có state. Khi React Hooks ra đời thì Function Component cũng có
state
của riêng nó.Class Component
state
trong Class Component dược định nghĩa như sau:import React, { Component } from 'react'; class TestComponent extends Components { constructor(props) { super(props); // khởi tạo giá trị state this.state = { isLoading: false }; } render() { return <div>TestComponent</div>; } }
Khi muốn thay đổi giá trị
state
bạn gọi phương thứcsetState
của component:this.setState((state) => ({ isLoading: true }));
Function Component
state
trong Function Component được định nghĩa như sau:import React, { useState } from 'react'; export function TestComponent(props) { // giá trị khởi tạo state được truyền vào trong useState hook const [state, setState] = useState({ isLoading: false }); return <div>TestComponent</div>; }
Các bạn để ý hàm
useState
trả về giá trị của componentstate
trong biếnstate
và hàmsetState
. Khi muốn thay đổi giá trị của state thì bạn có thể gọi hàmsetState
.setState({ isLoading: true });
Component Lifecycle
Với Class component các bạn sẽ thấy component lifecycle khá rõ ràng với các hàm như
componentDidMount
,componentDidUpdate
. Function Component thì không như vậy, toàn bộ việc sử lý lifecycle đều thông quauseEffect
hook.// componentDidMount useEffect(() => { return () => {}; // componentWillUnmount }, []);
// componentDidUpdate useEffect(() => { return () => {}; // componentWillUnmount }, [state]);
Như các bạn thấy thì
componentDidMount
vàcomponentDidUpdate
không chỉ định rõ khi nào thì hàm được gọi. Việc gọi hàm thì tự chúng ta hiểu dựa theo lifecycle của React component thôi. Với Function Component vàuseEffect
thì khác, bạn có thể thấy[]
và[state]
chị định rõ ràng đối tượng phụ thuộc mà khi chúng thay đổi thì hàm truyển vàouseEffect
sẽ được gọi. Có vẻ như nó lại tường mình hơn là các hàm lifecycle trong class Component.Sau sự có mặt của TypeScript thì có lẽ Class Component và React Hooks thì có lẽ bạn Class Component chiếm ưu thế tuyệt đối. Thời điểm đó anh em thi nhau viết Class Component với TypeScript và tôi cũng thế :). Ngay đến cả facebook cũng support TypeScript với create-react-app nữa cơ mà. Thế nhưng khi React Hooks xuất hiện thì có vẻ gió đã đổi chiều, việc xử lý state và lifecycle với hook có vẻ đơn giản hơn rất nhiều. Các bạn thì thấy thế nào. Bạn sẽ chọn cách nào với dự án của mình?