Reactjs Style

Mục lục

  1. Những luật cơ bản
  2. So sánh Class vs React.createClass vs stateless
  3. Mixins
  4. Đặt tên
  5. Khai báo
  6. Căn chỉnh mã nguồn
  7. Dấu nháy đơn và nháy kép
  8. Khoảng trắng
  9. Props
  10. Refs
  11. Dấu ngoặc đơn
  12. Thẻ
  13. Phương thức
  14. Cách sắp xếp hàm
  15. Thuộc tính isMounted

Những luật cơ bản

  • Chỉ chứa một React Component trong 1 file.
  • Tuy nhiên, những component có khả năng sử dụng lại(Stateless Component, hoặc Pure Components) có thể viết chung một file. eslint: react/no-multi-comp.
  • Luôn luôn sử dụng cú pháp JSX.  - Không sử dụng React.createElement chung với cú pháp JSX.

So sánh class vs React.createClass vs stateless

  • Nếu Component có state hoặc refs, nên sử dụng class extends React.Component thay vì React.createClass. eslint: react/prefer-es6-class react/prefer-stateless-function

    // tệ
    const Listing = React.createClass({
      // ...
      render() {
        return <div>{this.state.hello}</div>;
      }
    });
    
    // tốt
    class Listing extends React.Component {
      // ...
      render() {
        return <div>{this.state.hello}</div>;
      }
    }

    Và nếu trong Component không có state hoặc refs, nên sử dụng khai báo hàm (không phải arrow function) thay vì class:

    // tệ
    class Listing extends React.Component {
      render() {
        return <div>{this.props.hello}</div>;
      }
    }
    
    // tệ (dựa vào tên hàm để suy luận thì rất đau đầu)
    const Listing = ({ hello }) => (
      <div>{hello}</div>
    );
    
    // tốt
    function Listing({ hello }) {
      return <div>{hello}</div>;
    }

Mixins

Mixins tạo ra các implicit dependencies(phụ thuộc ngầm), gây ra xung đột tên và tăng độ phức tạp. Có thể thay thế mixins bằng components, higher-order components, hoặc các utility modules(gói tiện ích).

Đặt tên

  • Phần mở rộng(extensions): Sử dụng phần mở rộng .jsx cho React Components.

  • Tên file: Sử dụng chuẩn PascalCase cho tên file. Ví dụ: ReservationCard.jsx.

  • Tên tham chiếu(Reference Naming): Sử dụng PascalCase cho React components và dùng camelCase cho các đối tượng(instances) của chúng. eslint: react/jsx-pascal-case

    // tệ
    import reservationCard from './ReservationCard';
    
    // tốt
    import ReservationCard from './ReservationCard';
    
    // tệ
    const ReservationItem = <ReservationCard />;
    
    // tốt
    const reservationItem = <ReservationCard />;
  • Đặt tên Component: Sử dụng tên file trùng với tên component. Ví dụ: ReservationCard.jsx nên có tên tham chiếu là ReservationCard. Tuy nhiên, đối với các component gốc của một thư mục, hãy sử dụng index.jsx làm tên file và sử dụng tên thư mục làm tên component:

    // tệ
    import Footer from './Footer/Footer';
    
    // tệ
    import Footer from './Footer/index';
    
    // tốt
    import Footer from './Footer';
  • Đặt tên Higher-order Component: Sử dụng sự kết hợp của Higher-order component và tên của component đuợc truyền như displayName(tên hiển thị) trên component đuợc tạo ra. Ví dụ component bậc cao withFoo(), khi truyền một component Bar sẽ tạo ra một component với displayName của withFoo(Bar).

    Tại sao? Vì displayName của component có thể đuợc sử dụng bởi những công cụ phát triển hoặc trong các thông báo lỗi, và có một giá trị mà thể hiện rõ mối quan hệ này sẽ giúp chúng hiểu rõ chuyện gì đang xảy ra.

    // tệ
    export default function withFoo(WrappedComponent) {
      return function WithFoo(props) {
        return <WrappedComponent {...props} foo />;
      }
    }
    
    // tốt
    export default function withFoo(WrappedComponent) {
      function WithFoo(props) {
        return <WrappedComponent {...props} foo />;
      }
    
      const wrappedComponentName = WrappedComponent.displayName
        || WrappedComponent.name
        || 'Component';
    
      WithFoo.displayName = `withFoo(${wrappedComponentName})`;
      return WithFoo;
    }
  • Đặt tên Props: Tránh sử dụng tên props của DOM Component cho mục đích khác.

    Tại sao? Mọi nguời mong đợi props như style và className có ý nghĩa riêng. Việc thay đổi mục đích sử dụng của API gốc làm cho mã khó đọc và khó bảo trì hơn, thậm chí có thể gây ra lỗi.

    // tệ
    <MyComponent style="fancy" />
    
    // tệ
    <MyComponent className="fancy" />
    
    // tốt
    <MyComponent variant="fancy" />

Khai báo

  • Không nên sử dụng displayName để đặt tên cho các Components. Thay vào đó, đặt tên cho các Components bằng references(tham chiếu).

    // tệ
    export default React.createClass({
      displayName: 'ReservationCard',
      // một số thứ khác
    });
    
    // tốt
    export default class ReservationCard extends React.Component {
    }

Căn chỉnh mã nguồn

  • Căn chỉnh cho cú pháp JSX. eslint: react/jsx-closing-bracket-location react/jsx-closing-tag-location

    // tệ
    <Foo superLongParam="bar"
         anotherSuperLongParam="baz" />
    
    // tốt
    <Foo
      superLongParam="bar"
      anotherSuperLongParam="baz"
    />
    
    // Nếu props phù hợp trong một dòng thì giữ nó trên cùng một dòng
    <Foo bar="bar" />
    
    // Component con được thụt lề bình thường
    <Foo
      superLongParam="bar"
      anotherSuperLongParam="baz"
    >
      <Quux />
    </Foo>

Dấu nháy đơn và nháy kép

  • Luôn luôn sử dụng dấu ngoặc kép (") cho các thuộc tính JSX, nhưng dấu nháy đơn (') cho tất cả các JS khác. Eslint: jsx-quotes

    Tại sao? Vì các thuộc tính HTML thông thường thường sử dụng dấu ngoặc kép thay vì đơn, vì vậy thuộc tính JSX cũng như thế.

    // tệ
    <Foo bar='bar' />
    
    // tốt
    <Foo bar="bar" />
    
    // tệ
    <Foo style={{ left: "20px" }} />
    
    // tốt
    <Foo style={{ left: '20px' }} />

Khoảng trắng

  • Luôn luôn có duy nhất một kí tự space(khoảng trắng) trong thẻ tự đóng. eslint: no-multi-spacesreact/jsx-tag-spacing

    // tệ
    <Foo/>
    
    // rất tệ
    <Foo                 />
    
    // tệ
    <Foo
    />
    
    // tốt
    <Foo />
  • Không dùng khoảng trắng giữa giá trị bên trong ngoặc nhọn. eslint: react/jsx-curly-spacing

    // tệ
    <Foo bar={ baz } />
    
    // tốt 
    <Foo bar={baz} />

Props

  • Luôn luôn sử dụng camelCase khi đặt tên prop (camelCase : viết hoa chữa cái đầu của các từ , từ đầu tiên của cụm thì viết thường)

    // tệ
    <Foo
      UserName="hello"
      phone_number={12345678}
    />
    
    // tốt
    <Foo
      userName="hello"
      phoneNumber={12345678}
    />
  • Bỏ giá trị của prop khi nó thực sự rõ ràng là true. eslint: react/jsx-boolean-value

    // tệ
    <Foo
      hidden={true}
    />
    
    // tốt
    <Foo
      hidden
    />
    
    // tốt
    <Foo hidden />
  • Luôn luôn sử dụng prop alt trong thẻ <img>. Nếu giá trị của thẻ là NULL , alt có thể là một chuỗi rỗng hoặc <img> phải có thuộc tính role="presentation". eslint: jsx-a11y/alt-text

    // tệ
    <img src="hello.jpg" />
    
    // tốt
    <img src="hello.jpg" alt="Me waving hello" />
    
    // tốt
    <img src="hello.jpg" alt="" />
    
    // tốt
    <img src="hello.jpg" role="presentation" />
  • Không dùng các từ "image", "photo", hoặc "picture" trong <img> alt props. eslint: jsx-a11y/img-redundant-alt

    Tại sao? Screenreaders đã tự hiểu img elements là image(ảnh), vì vậy không cần khai báo thông tin này trong alt

    // tệ
    <img src="hello.jpg" alt="Picture of me waving hello" />
    
    // tốt
    <img src="hello.jpg" alt="Me waving hello" />
  • Chỉ sử dụng ARIA roles. eslint: jsx-a11y/aria-role hợp lệ, và không trừu tượng. jsx-a11y/aria-role

    // tệ - không phải ARIA roles
    <div role="datepicker" />
    
    //tệ- ARIA roles trừu tượng
    <div role="range" />
    
    // tốt
    <div role="button" />
  • Không dùng accessKey trong các elements. eslint: jsx-a11y/no-access-key

    Tại sao ? Sự mâu thuẫn giữa phím tắt và các lệnh bàn phím được những người dùng screenreaders sử dụng làm phức tạp hóa khả năng tiếp cận.

    // tệ
    <div accessKey="h" />
    
    // tốt
    <div />
  • Tránh dùng chỉ số của mảng(index) cho thuộc tính key, nên sử dụng một unique ID(định danh duy nhất). (why?)

    // tệ
    {todos.map((todo, index) =>
    <Todo
      {...todo}
      key={index}
    />
    )}
    
    // tốt
    {todos.map(todo => (
    <Todo
      {...todo}
      key={todo.id}
    />
    ))}
  • Luôn xác định rõ ràng các defaultProp(thuộc tính mặc định) cho tất cả non-required props(thuộc tính không bắt buộc).

    Tại sao? propTypes được coi như tài liệu, và cung cấp defaultProps , nghĩa là người đọc mã nguồn của bạn không cần phải đoán quá nhiều. Ngoài ra, nó có thể bỏ qua một số kiểm tra kiểu(type checking).

    // tệ
    function SFC({ foo, bar, children }) {
      return <div>{foo}{bar}{children}</div>;
    }
    SFC.propTypes = {
      foo: PropTypes.number.isRequired,
      bar: PropTypes.string,
      children: PropTypes.node,
    };
    
    // tốt
    function SFC({ foo, bar, children }) {
      return <div>{foo}{bar}{children}</div>;
    }
    SFC.propTypes = {
      foo: PropTypes.number.isRequired,
      bar: PropTypes.string,
      children: PropTypes.node,
    };
    SFC.defaultProps = {
      bar: '',
      children: null,
    };
  • Hạn chế lạm dụng toán tử spread cho việc truyền props

Tại sao? Vì bạn có khả năng truyền props không cần thiết xuống Components. Và với React v15.6.1 trờ lên, bạn cần chuyển các thuộc tính không hợp lệ của HTML sang DOM.

Ngoại lệ:

  • HOCs có thể truyền thẳng props xuống và khai báo propTypes

    function HOC(WrappedComponent) {
    return class Proxy extends React.Component {
      Proxy.propTypes = {
        text: PropTypes.string,
        isLoading: PropTypes.bool
      };
    
      render() {
        return <WrappedComponent {...this.props} />
      }
    }
    }
  • Sử dụng toán tử spread đối với prop được khai báo rõ ràng. Điều này có thể đặc biệt hữu ích khi test các React component với cấu trúc beforeEach của Mocha.

    export default function Foo {
    const props = {
      text: '',
      isPublished: false
    }
    
    return (<div {...props} />);
    }
  • Ghi chú: Nên lọc các props không cần thiết khi có thể. Ngoài ra, sử dụng prop-types-exact để giúp ngăn ngừa lỗi.

    // tốt
    render() {
      const { irrelevantProp, ...relevantProps  } = this.props;
      return <WrappedComponent {...relevantProps} />
    }
    
    // tệ
    render() {
      const { irrelevantProp, ...relevantProps  } = this.props;
      return <WrappedComponent {...this.props} />
    }

Refs

  • Luôn sử dụng hàm gọi lại(callback) cho khai báo ref. eslint: react/no-string-refs

    // tệ
    <Foo
      ref="myRef"
    />
    
    // tốt
    <Foo
      ref={(ref) => { this.myRef = ref; }}
    />

Dấu ngoặc đơn

  • Đóng gói các thẻ JSX trong ngoặc đơn khi chúng kéo dài nhiều dòng. eslint: react/jsx-wrap-multilines

    // tệ
    render() {
      return <MyComponent variant="long body" foo="bar">
               <MyChild />
             </MyComponent>;
    }
    
    // Tốt
    render() {
      return (
        <MyComponent variant="long body" foo="bar">
          <MyChild />
        </MyComponent>
      );
    }
    
    // Tốt, Khi chỉ có 1 dòng
    render() {
      const body = <div>hello</div>;
      return <MyComponent>{body}</MyComponent>;
    }

Thẻ

  • Luôn luôn tự đóng các thẻ(tags) không có con. eslint: react/self-closing-comp

    // tệ
    <Foo variant="stuff"></Foo>
    
    // tốt
    <Foo variant="stuff" />
  • Nếu Component của bạn có thuộc tính nhiều dòng, hãy đóng thẻ đó trên 1 dòng mới. eslint: react/jsx-closing-bracket-location

    // tệ
    <Foo
      bar="bar"
      baz="baz" />
    
    // tốt
    <Foo
      bar="bar"
      baz="baz"
    />

Phương thức

  • Sử dụng arrow function để bao đóng các biến cục bộ.

    function ItemList(props) {
      return (
        <ul>
          {props.items.map((item, index) => (
            <Item
              key={item.key}
              onClick={() => làmGìĐó(item.name, index)}
            />
          ))}
        </ul>
      );
    }
  • Các hàm binding được gọi trong lúc render nên đặt ở trong hàm khởi tạo(constructor). eslint: react/jsx-no-bind

    Tại sao? Vì nếu bind trong hàm render thì mỗi khi render, hàm đó lại được tạo mới một lần khiến cho hiệu suất xử lí giảm.

    // tệ
    class MyComponent extends React.Component {
      onClickDiv() {
        // làm việc gì đó
      }
    
      render() {
        return <div onClick={this.onClickDiv.bind(this)} />;
      }
    }
    
    // tốt
    class MyComponent extends React.Component {
      constructor(props) {
        super(props);
    
        this.onClickDiv = this.onClickDiv.bind(this);
      }
    
      onClickDiv() {
        // Làm gì đó vui vẻ
      }
    
      render() {
        return <div onClick={this.onClickDiv} />;
      }
    }
  • Không nên dùng dấu "_" đặt trước tên các hàm của Component

    Lí do? Vì dấu gạch dước thi thoảng được dùng trong một số ngôn ngữ để biểu thị tính "private". Tuy nhiên, không giống các ngôn ngữ khác, trong JavaScript, mọi thứ đều là “public”. Cho dù bạn có cho dấu gạch dưới vào hay không nó vẫn là public, bất kể ý định của bạn. Hãy xem vấn đề #1024, và #490 để hiểu sâu hơn.

    // tệ
    React.createClass({
      _onClickSubmit() {
        // làm việc gì đó
      },
    
      // làm việc gì đó
    });
    
    // tốt
    class extends React.Component {
      onClickSubmit() {
        // làm việc gì đó
      }
    
      // làm việc gì đó
    }
  • Phải trả về một giá trị trong hàm render. eslint: react/require-render-return

    // tệ
    render() {
      (<div />);
    }
    
    // tốt
    render() {
      return (<div />);
    }

Cách sắp xếp hàm

  • Các hàm trong class extends React.Component nên được viết theo thứ tự sau:
  1. Các phương thức tĩnh static (không bắt buộc)
  2. constructor
  3. getChildContext
  4. componentWillMount
  5. componentDidMount
  6. shouldComponentUpdate
  7. componentWillUpdate
  8. componentDidUpdate
  9. componentWillUnmount
  10. Hàm xử lí sự kiện như click hoặc submit onClickSubmit() & onChangeDescription()
  11. Các hàm lấy dữ liệu cho hàm render chẳng hạn như getSelectReason() hay getFooterContent()
  12. Các hàm render khác như renderNavigation() hay renderProfilePicture()
  13. render
  • Cách định nghĩa propTypesdefaultPropscontextTypes, ...

    import React from 'react';
    import PropTypes from 'prop-types';
    
    const propTypes = {
      id: PropTypes.number.isRequired, // Nếu id không đúng kiểu number, màn hình console sẽ hiện ra cảnh báo
      url: PropTypes.string.isRequired, // Nếu url không đúng kiểu string, màn hình console sẽ hiện ra cảnh báo
      text: PropTypes.string, // Nếu text không đúng kiểu string, màn hình console sẽ hiện ra cảnh báo
    };
    
    const defaultProps = {
      text: 'Hello World',  // Gán giá trị mặc định cho text
    };
    
    class Link extends React.Component {
      static methodsAreOk() {
        return true;
      }
    
      render() {
        return <a href={this.props.url} data-id={this.props.id}>{this.props.text}</a>;
      }
    }
    
    Link.propTypes = propTypes;
    Link.defaultProps = defaultProps;
    
    export default Link;
  • Các hàm trong React.createClass nên được viết theo thứ tự sau: eslint: [react/sort-comp`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md)

  1. displayName
  2. propTypes
  3. contextTypes
  4. childContextTypes
  5. mixins
  6. statics
  7. defaultProps
  8. getDefaultProps
  9. getInitialState
  10. getChildContext
  11. componentWillMount
  12. componentDidMount
  13. componentWillReceiveProps
  14. shouldComponentUpdate
  15. componentWillUpdate
  16. componentDidUpdate
  17. componentWillUnmount
  18. Hàm xử lí sự kiện như onClickSubmit() hay onChangeDescription()
  19. Các hàm lấy dữ liệu cho phương thức render nhưgetSelectReason() hay getFooterContent()
  20. Các hàm render khác như renderNavigation() hay renderProfilePicture()
  21. render

Thuộc tính isMounted

Tại sao? Vì isMounted là một anti-pattern(mẫu nên tránh), không có sẵn khi dùng ES6 classes, và đang bị phản đối từ cộng đồng.

CI & CD with Docker and Nginx

 Essential steps in deploying

1. Build/Compile the app

2. Take the content of the dist directory and add them to our Nginx docker image.

3. Serve the application from Nginx


Angular Build Changes 
config at package.json
"scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --aot --buildOptimizer=true --prod --progress",
    "test": "ng test  --watch=false",
    "lint": "ng lint",
    "e2e": "ng e2e"
  }

NGINX config and Docker
# service nginx restart * Restarting nginx # ps -ef --forest | grep nginx root 32475 1 0 13:36 ? 00:00:00 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf nginx 32476 32475 0 13:36 ? 00:00:00 _ nginx: worker process nginx 32477 32475 0 13:36 ? 00:00:00 _ nginx: worker process nginx 32479 32475 0 13:36 ? 00:00:00 _ nginx: worker process nginx 32480 32475 0 13:36 ? 00:00:00 _ nginx: worker process nginx 32481 32475 0 13:36 ? 00:00:00 _ nginx: cache manager process nginx 32482 32475 0 13:36 ? 00:00:00 _ nginx: cache loader process

NGINX config:
worker_processes  auto;

events {
    worker_connections  8192;
}


http {
  # formatting log to json
  log_format json_combined escape=json
  '{'
    '"time_local":"$time_local",'
    '"remote_addr":"$remote_addr",'
    '"remote_user":"$remote_user",'
    '"request":"$request",'
    '"status": "$status",'
    '"body_bytes_sent":"$body_bytes_sent",'
    '"request_time":"$request_time",'
    '"http_referrer":"$http_referer",'
    '"http_user_agent":"$http_user_agent"'
  '}';
    server {
        listen 80;
        server_name  localhost;
        #sending logs to console (standard out) in a predefined json fromat
        access_log /dev/stdout json_combined;
        error_log /dev/stdout info json_combined;
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        include /etc/nginx/mime.types;
        # compression
        gzip on;
        gzip_min_length 1000;
        gzip_proxied expired no-cache no-store private auth;
        gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

        # angular index.html location
        location / {
            try_files $uri $uri/ /index.html;
        }
        # potential reverse proxy for sending api calls
        location /reverse-proxy/ {
                proxy_set_header Host $host;
                proxy_set_header Content-Type application/json;
                proxy_set_header X-Real-IP $remote_addr;
                # proxy_pass http://pointer-api:8080/;
          }
    }
}
Dockerfile config:
FROM nginx:1.17.8-alpine
COPY nginx/nginx.conf /etc/nginx/nginx.conf
WORKDIR /usr/share/nginx/html/
COPY dist/heroes .

Digital Ocean - Docker VM
# Create User
$ usermod -aG sudo deployer
$ adduser deployer
$ su -  deployer
$ sudo usermod -aG docker $USER
$ docker ps

GitLab CI/CD
1. Configuration

Before going into gitlab-ci.yml, there are a few variables that must be defined under /settings/ci_cd :

  1. CI_REGISTRY_USER — GitLab user
  2. DOCKER_CI_TOKEN — GitLab token used by docker login instead of a password in command: docker login -u $CI_REGISTRY_USER -P $DOCKER_CI_TOKEN registry.gitlab.com. They are generated from here. Make sure to have api and read_registry checked.
  3. IMAGE_NAME — docker image name. Your image name needs to follow this convention: registry.gitlab.com/$GROUP_NAME/$PROJECT_NAME:tag, for this reason, our image name will be: registry.gitlab.com/blogging4t/angular-ci-cd
  4. Next are the SERVER_IP_ADDRESS and the user for it, SERVER_USER that we created in the Digital Ocean section

Lastly, we must generate a custom RSA key to be able to ssh from GitLab to our server:

  1. ssh-keygen -t rsa -b 4096
  2. cd ~/.ssh
  3. cat gitlab
  4. Copy/Paste the private key in your PRIVATE_KEY variable on Gitlab.
  5. ssh on a server with root and execute the following commands:
    cp -r ~/.ssh/ /home/deployer
    cd /home/deployer/.ssh
    chown deployer:deployer *
    chmod 644 authorized_keys
    #edit your authorized keys file and add gitlab.pub

PIPELINE:
# docker image, we need a image with chrome installed so we can run unit tests in a virtualized environment such as docker containers
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-9.6-graphicsmagick-1.3.34"

#the majority of our builds require npm dependencies
before_script:
  - npm install

# thre stages, build -> unit tests and compilation, release -> docker image creation, deploy-> start created image on a remote server
stages:
  - build
  - release
  - deploy

build:
  stage: build #due to this common stage we have paralel builds to gain some speed
  script:
    - npm run-script build # makes sure we can still build our code
  artifacts: #take the output of the compilation and transfer it to gain speed and create a docker image out of it
    paths:
      - dist/
tests:
  stage: build #due to this common stage we have paralel builds to gain some speed
  script:
    - npm test #runs unit tests

image-creation:
  image: docker:git # image with docker installed to execute docker commands
  stage: release # notice a new stage
  services:
    - docker:dind #used to be able to execute docker commands inside of a docker container
  before_script:
    - docker ps #overrides previous docker script
  script:
    # Non interactive ssh gracefully reloads server
    - docker login -u $CI_REGISTRY_USER -p $DOCKER_CI_TOKEN registry.gitlab.com #logs into gitlab docker registery, make sure to have this variables defined
    - docker build -t $IMAGE_NAME . # creates a docker image
    - docker push $IMAGE_NAME:latest # pushes the create docker image to docker registry
  dependencies:
    - build #dependent on build to get the dist directory


deploy:
  image: ubuntu
  before_script: #checks if ssh installed and if not, attempts to install it
    - "which ssh-agent || ( apt-get update -y && apt-get install openssh-client git -y )"
    - eval $(ssh-agent -s) Essential steps in deploying

1. Build/Compile the app

2. Take the content of the dist directory and add them to our Nginx docker image.

3. Serve the application from Nginx


Angular Build Changes 
config at package.json
"scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --aot --buildOptimizer=true --prod --progress",
    "test": "ng test  --watch=false",
    "lint": "ng lint",
    "e2e": "ng e2e"
  }

NGINX config and Docker
# service nginx restart
* Restarting nginx
# ps -ef --forest | grep nginx
root     32475     1  0 13:36 ?        00:00:00 nginx: master process /usr/sbin/nginx 
                                                -c /etc/nginx/nginx.conf
nginx    32476 32475  0 13:36 ?        00:00:00  _ nginx: worker process
nginx    32477 32475  0 13:36 ?        00:00:00  _ nginx: worker process
nginx    32479 32475  0 13:36 ?        00:00:00  _ nginx: worker process
nginx    32480 32475  0 13:36 ?        00:00:00  _ nginx: worker process
nginx    32481 32475  0 13:36 ?        00:00:00  _ nginx: cache manager process
nginx    32482 32475  0 13:36 ?        00:00:00  _ nginx: cache loader process

NGINX config:
worker_processes  auto;

events {
    worker_connections  8192;
}


http {
  # formatting log to json
  log_format json_combined escape=json
  '{'
    '"time_local":"$time_local",'
    '"remote_addr":"$remote_addr",'
    '"remote_user":"$remote_user",'
    '"request":"$request",'
    '"status": "$status",'
    '"body_bytes_sent":"$body_bytes_sent",'
    '"request_time":"$request_time",'
    '"http_referrer":"$http_referer",'
    '"http_user_agent":"$http_user_agent"'
  '}';
    server {
        listen 80;
        server_name  localhost;
        #sending logs to console (standard out) in a predefined json fromat
        access_log /dev/stdout json_combined;
        error_log /dev/stdout info json_combined;
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        include /etc/nginx/mime.types;
        # compression
        gzip on;
        gzip_min_length 1000;
        gzip_proxied expired no-cache no-store private auth;
        gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

        # angular index.html location
        location / {
            try_files $uri $uri/ /index.html;
        }
        # potential reverse proxy for sending api calls
        location /reverse-proxy/ {
                proxy_set_header Host $host;
                proxy_set_header Content-Type application/json;
                proxy_set_header X-Real-IP $remote_addr;
                # proxy_pass http://pointer-api:8080/;
          }
    }
}
Dockerfile config:
FROM nginx:1.17.8-alpine
COPY nginx/nginx.conf /etc/nginx/nginx.conf
WORKDIR /usr/share/nginx/html/
COPY dist/heroes .

Digital Ocean - Docker VM
# Create User
$ usermod -aG sudo deployer
$ adduser deployer
$ su -  deployer
$ sudo usermod -aG docker $USER
$ docker ps

GitLab CI/CD
1. Configuration
Before going into gitlab-ci.yml, there are a few variables that must be defined under /settings/ci_cd :

CI_REGISTRY_USER — GitLab user
DOCKER_CI_TOKEN — GitLab token used by docker login instead of a password in command: docker login -u $CI_REGISTRY_USER -P $DOCKER_CI_TOKEN registry.gitlab.com. They are generated from here. Make sure to have api and read_registry checked.
IMAGE_NAME — docker image name. Your image name needs to follow this convention: registry.gitlab.com/$GROUP_NAME/$PROJECT_NAME:tag, for this reason, our image name will be: registry.gitlab.com/blogging4t/angular-ci-cd
Next are the SERVER_IP_ADDRESS and the user for it, SERVER_USER that we created in the Digital Ocean section
Lastly, we must generate a custom RSA key to be able to ssh from GitLab to our server:

ssh-keygen -t rsa -b 4096
cd ~/.ssh
cat gitlab
Copy/Paste the private key in your PRIVATE_KEY variable on Gitlab.
ssh on a server with root and execute the following commands:
cp -r ~/.ssh/ /home/deployer
cd /home/deployer/.ssh
chown deployer:deployer *
chmod 644 authorized_keys
#edit your authorized keys file and add gitlab.pub

PIPELINE:
# docker image, we need a image with chrome installed so we can run unit tests in a virtualized environment such as docker containers
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-9.6-graphicsmagick-1.3.34"

#the majority of our builds require npm dependencies
before_script:
  - npm install

# thre stages, build -> unit tests and compilation, release -> docker image creation, deploy-> start created image on a remote server
stages:
  - build
  - release
  - deploy

build:
  stage: build #due to this common stage we have paralel builds to gain some speed
  script:
    - npm run-script build # makes sure we can still build our code
  artifacts: #take the output of the compilation and transfer it to gain speed and create a docker image out of it
    paths:
      - dist/
tests:
  stage: build #due to this common stage we have paralel builds to gain some speed
  script:
    - npm test #runs unit tests

image-creation:
  image: docker:git # image with docker installed to execute docker commands
  stage: release # notice a new stage
  services:
    - docker:dind #used to be able to execute docker commands inside of a docker container
  before_script:
    - docker ps #overrides previous docker script
  script:
    # Non interactive ssh gracefully reloads server
    - docker login -u $CI_REGISTRY_USER -p $DOCKER_CI_TOKEN registry.gitlab.com #logs into gitlab docker registery, make sure to have this variables defined
    - docker build -t $IMAGE_NAME . # creates a docker image
    - docker push $IMAGE_NAME:latest # pushes the create docker image to docker registry
  dependencies:
    - build #dependent on build to get the dist directory


deploy:
  image: ubuntu
  before_script: #checks if ssh installed and if not, attempts to install it
    - "which ssh-agent || ( apt-get update -y && apt-get install openssh-client git -y )"
    - eval $(ssh-agent -s)
    # Inject the remote's private key
    - echo "$PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null #adding a ssh private key from variables, pair of the one registered on digital ocean
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    # Append keyscan output into known hosts
    - ssh-keyscan $SERVER_IP_ADDRESS >> ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
  stage: deploy #new stage after release
  script:
    # Non interactive ssh gracefully reloads server
    - ssh $SERVER_USER@$SERVER_IP_ADDRESS  ls
    - ssh $SERVER_USER@$SERVER_IP_ADDRESS "docker login -u ${CI_REGISTRY_USER} -p ${DOCKER_CI_TOKEN} registry.gitlab.com;
     docker stop angular-ci;
     docker rm angular-ci; 
     docker rmi "$(docker images -aq)"
     docker pull ${IMAGE_NAME};
     docker run --name angular-ci -d -p 80:80 ${IMAGE_NAME}"

  only:
    # Trigger deployments only from master branch
    - master
  dependencies:
   - image-creation #dependency on creating a docker image with latest changes

Glossary

    # Inject the remote's private key
    - echo "$PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null #adding a ssh private key from variables, pair of the one registered on digital ocean
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    # Append keyscan output into known hosts
    - ssh-keyscan $SERVER_IP_ADDRESS >> ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
  stage: deploy #new stage after release
  script:
    # Non interactive ssh gracefully reloads server
    - ssh $SERVER_USER@$SERVER_IP_ADDRESS  ls
    - ssh $SERVER_USER@$SERVER_IP_ADDRESS "docker login -u ${CI_REGISTRY_USER} -p ${DOCKER_CI_TOKEN} registry.gitlab.com;
     docker stop angular-ci;
     docker rm angular-ci; 
     docker rmi "$(docker images -aq)"
     docker pull ${IMAGE_NAME};
     docker run --name angular-ci -d -p 80:80 ${IMAGE_NAME}"

  only:
    # Trigger deployments only from master branch
    - master
  dependencies:
   - image-creation #dependency on creating a docker image with latest changes

The OCR Service to extract the Text Data

Optical character recognition, or OCR, is a key tool for people who want to build or collect text data. OCR uses machine learning to extract...