Use Github Action to deploy React app to Surge

  • Generate a personal access token and create ACCESS_TOKEN variable under Settings -> Secrets.
  • Keep the personal access token in a safe place and do not lose it

npm script commands

  • Add script commands “build” and “clean” to build application and generate artifacts in dist/ directory
  "scripts": {
    "dev": "parcel src/index.html",
    "build": "parcel build src/index.html",
    "clean": "rm -rf ./dist/"
  }
  • Run “npm run build” to build static application before deployment to surge.sh

Surge setup

  • npm install surge globally
npm install -g surge
  • Login or create surge.sh account in command line
surge
email: <email address>
password: <password>

project:  <project directory>/dist
domain: <custom domain>.surge.sh
  • After the site is published, verify http://<custom domain>.surge.sh can be browsed

Surge Token

  • Generate surge token in command line
surge token
XXXXXXXXX        <-- a surge token is issued by Surge
  • In Github repo, create SURGE_TOKEN variable under Settings -> Secrets.
  • Keep surge token in safe location such that it can be reused to deploy other applications to Surge.

Create Github Action workflow file

  • Go to Actions tab of the github repo
  • Create surge.yml under .github/workflow

Paste the follow code in the yaml file

name: Deploy to surge.sh
on:
  push:
   branches:
    - master
jobs:
   build:
     name: Deploying to surge
     runs-on: ubuntu-latest
     steps:
       - name: Setup Node.js for use with actions
         uses: actions/setup-node@v1.1.0
         with:
           version:  12.x
      
       - name: Checkout branch
         uses: actions/checkout@v2
 
       - name: Clean install dependencies
         run: npm ci
 
       - name: Build app
         run: npm run build

       - name: Rename index.html to 200.html
         run: mv ./dist/index.html ./dist/200.html

       - name: Install Surge
         run: npm install -g surge
        
       - name: Deploy to Surge
         run:  surge ./dist https://<custom domain>.surge.sh --token ${{secrets.SURGE_TOKEN}}
  • Replace <custom domain> with actual surge domain name

Add 200.html page for Client-side routing

  • When page is refreshed in Surge, the url does not reach our Reach Router and default Surge 404 page is returned.
  • The solution is to rename dist/index.html to dist/200.html before deployment to Surge is carried out.
  • This is done by mv ./dist/index.html ./dist/200.html in surge.yaml
  • The reason is to load the app when 200 response is resulted. Then the app loads the appropriate component by matching the path of reach router to the url

References:

Use Github Action to deploy React app to Netlify

  • Generate a personal access token and create ACCESS_TOKEN variable under Settings -> Secrets.
  • Keep the personal access token in a safe place and do not lose it

Fix 404 not found when page is refreshed

My React app uses Reach Router library to route user from root / to /countries/:language. When I refreshes page with F5, reach router does not handle redirection and 404 page is returned.

In order to solve the problem in Netlify, I define a redirect rule in netlify.toml to redirect all routes to /index.html with HTTP status code 200.

  • Create netlify.toml in project directory
# Redirects and headers are GLOBAL for all builds – they do not get scoped to
# contexts no matter where you define them in the file.
# For context-specific rules, use _headers or _redirects files, which are
# PER-DEPLOY.

# A basic redirect rule
[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

Update package.json to create npm script commands that Github Action workflow file depends

  • “npm run clean” deletes all files in dist/ directory
  • “npm run build” builds project and generates artifacts in dist/ directory
  "scripts": {
    "build": "parcel build src/index.html",
    "clean": "rm -rf ./dist/"
  }
  • Go to Actions tab of the github repo
  • Create netlify.yml under .github/workflow

Paste the follow code in the yaml file

# .github/workflows/netlify.yml
name: Build and Deploy to Netlify
on:
  push:
  pull_request:
    types: [opened, synchronize]
jobs:
  build:
    name: Deploying to netlify
    runs-on: ubuntu-latest
    steps:
      - name: Setup Node.js for use with actions
        uses: actions/setup-node@v1.1.0
        with:
          version:  12.x
      
      - name: Checkout branch
        uses: actions/checkout@v2
 
      # ( Build to ./dist or other directory... )
      - name: Clean install dependencies
        run: npm ci

      - name: Remove dist
        run: npm run clean

      - name: Build app
        run: npm run build
 
      - name: Deploy to Netlify
        uses: nwtgck/actions-netlify@v1.1.10
        with:
          publish-dir: './dist'
          netlify-config-path: './netlify.toml'
          production-branch: master
          github-token: ${{ secrets.ACCESS_SECRET }}
          deploy-message: "Deploy from GitHub Actions"
          enable-pull-request-comment: false
          enable-commit-comment: true
          overwrites-pull-request-comment: true
        env:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
        timeout-minutes: 1

netlify-config-path indicates the configuration path to Netlify platform.

Netlify environment variables

Login to Netlify to look up NETIFY_SITE_ID and NETIFY_AUTH_TOKEN.

Go to Team > Site > Settings > Site Information > API ID to copy down the NETLIFY_SITE_ID

Go to User Settings > Applications > Personal access token and clicks New Access Token button to generate a token. The token is your NETLIFY_AUTH_TOKEN and it should be kept in a safe location.

Now, you are ready to create NETLIFY_SITE_ID and NETLIFY_AUTH_TOKEN variables under Settings -> Secrets in Github.

We are done! There is no need to write custom deployment script and travis configuration file to automate the CI/CD process. Github notifies Netlify to automate the build process and publish the site when latest changes are committed.

References:

Add apollo-client to a React project

Set up Apollo Client for React

  • npm install apollo/client and graphql
npm install @apollo/client graphql
  • Create client.tsx file to store configuration of apollo-client react
  • import ApolloClient, InMemoryCache and HttpLink from ‘@apollo/client’
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
  • Initialize and export an instance of ApolloClient
const uri = 'https://countries-274616.ew.r.appspot.com';
const link = new HttpLink({ uri });

const client = new ApolloClient({
    cache: new InMemoryCache(),
    link
});

export default client;
  • Test client instance in client.tsx by executing a GraphQL query
  • Create a new folder named graphql and add get-languages.tsx in it
  • graphql/get-languages.tsx defines a GraphQL query that returns countries where official language is English.
graphql/get-languages.tsx

import { gql } from '@apollo/client';

export const GET_LANGUAGES = gql`
    query getLanguages {
        languages: Language(
            filter: { name_in: ["English"] },   
            orderBy: [name_asc]) {
                id: _id
                name
                nativeName
                countries {
                    id: _id
                    name
                    flag {
                        emoji
                    }
                }
        }
    }
`;
  • Verify Apollo client can retrieve the results of GET_LANGUAGES and output to console
In client.tsx

import { GET_LANGUAGES } from './graphql/queries';

client.query({
  query: GET_LANGUAGES
}).then(console.log);

Connect Apollo Client to React

  • Import ApolloProvider component and client instance to App.tsx. The client instance is initialized and exported from client.tsx
  • Wrap ApolloProvider around top level element in App function
import { ApolloProvider } from "@apollo/client";
import client from './client';

const App = () => {
  return (
    <ApolloProvider client={client}>
      <React.StrictMode>
        <p>Hello World!</p>
      </React.StrictMode>
    </ApolloProvider>
  );
}

ReactDOM.render(<App/>, document.getElementById('root'));
  • Up to this point, the application can take advantage of Apollo Client to execute queries and mutations on client side

Set up a React and TypeScript project from scratch

  • Create a folder ts-graphql-country and change to directory
mkdir ts-graphql-country
cd ts-graphql-country
  • Generate package.json
  • Add “browserslist”: [ “last 2 Chrome versions” ] in package.json
npm init
"browserslist": [
   "last 2 Chrome versions"
]
  • Install react, react-dom and @reach/router
  • Install parcel bundler and prettier
npm install react react-dom @reach/router
npm install -D parcel-bundler prettier
  • Add dev script in package.json to launch web site at http://localhost:1234
  • Create a blank index.html in src folder
"scripts" {
  "dev": "parcel src/index.html"
}
  • Create .prettierrc file to store prettier configurations
{
    "trailingComma": "all",
    "tabWidth": 2,
    "semi": true,
    "singleQuote": true
}
  • Install @babel/core, @babel/preset-env and @babel/react
npm install -D @babel/core @babel/preset-env @babel/preset-react
  • Create .babelrc file and include babel presets as follows:
{
    "presets": ["@babel/preset-react", "@babel/preset-env"]
}
  • Install typescript, @types/react, @types/react-dom and @types/reacth__router
  • Install tslint, tslint-react and tslint-config-prettier
  • Run npx tsc –init to generate tsconfig.json
npm install -D typescript
npm install -D @types/react @types/react-dom @types/reach__router
npm install -D tslint tslint-react tslint-config-prettier
npx tsc --init
  • Open tsconfig.json. Update target to “ES2018”, uncomment “jsx” field and replace the value to “react”
  • Generate tslint.json and add the following
{
  "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
  "rules": {
    "ordered-imports": false,
    "object-literal-sort-keys": false,
    "member-ordering": false,
    "no-console": false,
    "jsx-no-lambda": false
  }
}
  • Add “lint”: “tslint –project .” script in package.json
"scripts": {
...
    "lint": "tslint --project ."
}
  • Install node-sass
npm install node-sass
  • Create blank scss file, style.scss, in src folder
  • Create an App component in App.tsx in src folder and write simple JavaScript code to render a h1 tag
import React from 'react';
import ReactDOM from 'react-dom';

const App = () => {
    return (
        <h1>Hello</h1>  
    );
}

ReactDOM.render(<App />, document.getElementById('root'));
  • Import style.scss and App.tsx to index.html to render App component
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <link rel="stylesheet" href="./style.scss" />
    <title>TS GraphQL Country List</title>
  </head>

  <body>
    <div id="root">not rendered</div>
    <script src="./App.tsx"></script>
  </body>
</html>
  • In terminal, type npm run dev to start web application.
  • In browser, the following is displayed

Use CSS to draw diagonal line across of square

  1. Create two divs and convert them into flexboxes.
.square {
   position: relative;
   width: 130px;
   height: 130px;
   border: 1px solid black;
   background: red;

   display: flex;
   justify-content: center;
   align-items: center;
}

<div class="square left-diag"></div>
<div class="square right-diag"></div>

2. Add .left-diag:after pseudo element to draw a black dialog line that draws from bottom left to upper right. Sass style is shown below:

.square {
   &.left-diag:after {
     content: "";
     position: absolute;
     z-index: 1;
     border: 1px solid black;
     height: 140%;
     transform: rotate(-45deg);
   }
}

3. Add .right-diag:after pseudo element to draw a black dialog line that draws from bottom right to upper left. Sass style is shown below:

.square {
   &.right-diag:after {
     content: "";
     position: absolute;
     z-index: 1;
     border: 1px solid black;
     height: 140%;
     transform: rotate(45deg);
   }
}

4. Lets refactor the sass styles and group common css properties in a mixin function

@mixin strikethroughDiagonal($rotation) {
  content: "";
  position: absolute;
  z-index: 1;
  border: 1px solid black;
  height: 140%;
  transform: rotate($rotation);
}

.square {
   &.left-diag:after {
     @include strikethroughDiagonal(-45deg); 
   }

   &.right-diag:after {
     @include strikethroughDiagonal(45deg); 
   }
}

Stackblitz repo:

https://stackblitz.com/edit/js-4s7qhi

Use Github Action to deploy Vue app to Netlify

  • Generate a personal access token and create ACCESS_TOKEN variable under Settings -> Secrets.
  • Keep the personal access token in a safe place and do not lose it
  • Go to Actions tab of the github repo
  • Create netlify.yml under .github/workflow
  • Paste the follow code in the yaml file
# .github/workflows/netlify.yml
name: Build and Deploy to Netlify
on:
  push:
  pull_request:
    types: [opened, synchronize]
jobs:
  build:
    name: Deploying to Netlify
    runs-on: ubuntu-latest
    steps:
      - name: Setup Node.js for use with actions
        uses: actions/setup-node@v1.1.0
        with:
           version:  12.x

      - name: Check out branch
        uses: actions/checkout@v2

      # ( Build to ./dist or other directory... )
      - name: Clean install dependencies
        run: npm ci

      - name: Build app
        run: npm run build-netlify

      - name: Deploy to Netlify
        uses: nwtgck/actions-netlify@v1.1
        with:
          publish-dir: './dist'
          production-branch: master
          github-token: ${{ secrets.ACCESS_SECRET }}
          deploy-message: "Deploy from GitHub Actions"
          enable-pull-request-comment: false
          enable-commit-comment: true
          overwrites-pull-request-comment: true
        env:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
        timeout-minutes: 1

I deploy the Tic Tac Toe game to both github page and Netlify; therefore, I use NODE_ENV variable to configure the public path in vue.config.js.

// vue.config.js
module.exports = {
  publicPath: process.env.NODE_ENV === "production" ? "/vue-tic-tac-toe/" : "/",
  outputDir: "dist",
  lintOnSave: process.env.NODE_ENV !== "production",
  devServer: {
    overlay: {
      warnings: true,
      errors: true
    }
  }
};

The NODE_ENV variable of github page is “production” by default and the full URL becomes https://<username>.github.io/vue-tic-tac-toe/. For Netlify deployment, I set the NODE_ENV variable to “netlify” and the game is hosted on https://<netlify app>/ .

Open package.json and add a task that builds the project to dist directory and publishes it to Netlify.

{
  ...
  "scripts": {
    ...
    "build-netlify": "NODE_ENV=netlify vue-cli-service build"
  },
  ...
}

Login to Netlify to look up NETIFY_SITE_ID and NETIFY_AUTH_TOKEN.

Go to Team > Site > Settings > Site Information > API ID to copy down the NETLIFY_SITE_ID

Go to User Settings > Applications > Personal access token and clicks New Access Token button to generate a token. The token is your NETLIFY_AUTH_TOKEN and it should be kept in a safe location.

Now, you are ready to create NETLIFY_SITE_ID and NETLIFY_AUTH_TOKEN variables under Settings -> Secrets in Github.

We are done! There is no need to write custom deployment script and travis configuration file to automate the CI/CD process. Github notifies Netlify to automate the build process and publish the site when latest changes are committed.

References:

Use github action to deploy vue app to github page

  • Generate a personal access token and create ACCESS_TOKEN variable under Settings -> Secrets.
  • Keep the personal access token in a safe place and do not lose it
  • Go to Actions tab of the github repo
  • Create main.yml under .github/workflow
 name: Deploy to github pages
 on:
   push:
    branches:
     - master
 jobs:
    build:
      name: Deploying to gh-pages
      runs-on: ubuntu-latest
      steps:
        - name: Setup Node.js for use with actions
          uses: actions/setup-node@v1.1.0
          with:
            version:  12.x
        - name: Checkout branch
          uses: actions/checkout@v2

        - name: Clean install dependencies
          run: npm ci

        - name: Build app
          run: npm run build
        
        - name: deploy
          uses: peaceiris/actions-gh-pages@v3
          with:
           github_token: ${{ secrets.ACCESS_TOKEN }}
           publish_dir: ./dist

There is no need to write custom deployment script and travis configuration file to automate the CI/CD process. Github does it for you when latest changes are pushed to master to start the build process and copy the contents of output folder to gh-pages branch.

References:

Use Github Action to deploy Angular app to github page

  • Generate a personal access token and create ACCESS_TOKEN variable in Settings -> Secrets.
  • Keep the personal access token in a safe place and do not lose it
  • Go to Actions tab of the github repo
  • Create main.yml under .github/workflow
name: CI
on:
  push:
    branches:
      - master
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          persist-credentials: false

      - name: Install
        run: |
          npm install
          npm run github-page-deploy
      - name: Build and Deploy Repo
        uses: JamesIves/github-pages-deploy-action@releases/v3
        with:
          ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
          BASE_BRANCH: master
          BRANCH: gh-pages
          CLEAN: true
          GIT_CONFIG_NAME: <author name>
          GIT_CONFIG_EMAIL: <email address>
          FOLDER: dist/<repo name>

References:

Deploy angular app to Github Page by Travis CI

1) Install npm dependencies

angular-cli-ghpages is a plugin that pushes angular application to gh-pages branch.

npm install angular-cli-ghpages --save-dev 

Add prettier schematic to Angular project

npm install -g @schuchard/prettier
ng g @schuchard/prettier:add

2) Create a .travis.yml file that details the deployment steps

3) Specify language is node_js and node version is 8

language: node_js
node_js:
    - '8'

4) Cache npm dependencies and production build directory, dist.

cache:
    npm: true
    directories:
        - node_modules
        - dist/

5) Create environment variables to store github organization, repo name, github username and github email respectively. GH_TOKEN is your secret github token that is saved under Settings of your repository. You can find the link to generate github token here

env:
    global:
        - GITHUB_ORG="https://GH_TOKEN@github.com"
        - REPO_NAME="<repository name>"
        - GITHUB_NAME="<username>"
        - GITHUB_EMAIL="<email address>"

6) Add “before_script” to install npm dependencies without modifying package-lock.json file

before_script:
    - npm install --no-save

7) Add “script” to run different tasks before the final deployment

script:
    - npm audit
    - npm run prettier
    - npm run lint
    - npm run test-headless
    - npm run build-ngh

branches:
    only:
        - master

8) The above tasks are created in scripts of package.json:

  • npm audit audits npm dependencies to ensure vulnerabilities do not exist
  • npm run prettier formats files with Prettier code formatter
  • npm run lint performs linting on source files to identify any lint problem and aborts the process prematurely
  • npm run test-headless executes test cases in headless Chrome browser and terminates if any test case fails
  • npm run build-ngh compiles the source codes on master branch to production build and places the artifacts to dist/<project> directory
"scripts": {
    "prettier": "prettier --write \"**/*.{js,json,scss,ts}\"",
    "lint": "ng lint",
    "build-ngh": "ng build --prod --base-href \"https://railsstudent.github.io/<repository name>/\"",
    "test-headless": "ng test --watch=false --browsers=ChromeHeadless"
  }

9) If all script tasks finishes successfully, the task in “after_success” is executed to push the static pages to gh_pages branch

after_success:
    - ngh --repo="$GITHUB_ORG/$GITHUB_NAME/$REPO_NAME.git" --name="$GITHUB_NAME" --email="$GITHUB_EMAIL" --dir=dist/ng-rxjs-state-management-demo --no-silent

10) Add notifications configuration to receive notification email when travis build fails

notifications:
email:
recipients:
- somebody@example.come
on_success: never # default: change
on_failure: always # default: always

11) Navigate to https://<username>.github.io/<repo name>/ to view the static pages