Week 1: Project Management

#React.js #Gatsby #GraphQL #GitLab CI/CD #Version Control #Git #Web Development


Assignments: Build a personal site describing you and your final project, upload it to the class archive and work through a git tutorial.

Published on: Jan 29, 2020
Last updated on: Jan 12, 2021

View Source Files


Read the Fab Charter

To me, the documentations and online resources that the fab academy community has provided are really useful. It offers various ways of thinking and making almost everthing by using open source tools such as mods and OpenSCAD.


As Git is a distributed version-control system, it could be used as a server out of the box. It's shipped with built-in command git daemon which starts simple TCP server running on the GIT protocol. Dedicated Git HTTP servers help (amongst other features) by adding access control, displaying the contents of a Git repository via the web interfaces, and managing multiple repositories. Already existing Git repositories can be cloned and shared to be used by others as a centralized repo. It can also be accessed via remote shell just by having the Git software installed and allowing a user to log in. Git servers typically listen on TCP port 9418.

Git

Setup

  • git init initialize a repository
  • git config --global user.name "Firstname Lastname" set name for commits
  • git config --global user.email "address@site" set email for commits

Add & Commit

  • git remote add origin [SERVER_URL] push to a remot repository (ex. GitHub or GitLab)
  • git add . stage all changed files to commit
  • git commit -m "commit message" commit changes

Update & Merge

  • git push push changes
  • git merge [BRANCH_NAME] merge branch

This project website is built based on React.js framework as its front-end development and Node.js as its back-end by adopting Gatsby framework which can produce static React.js website and makes it possible to host on GitLab Pages.

Why React.js?

React.js is a component based programming language, which means that I can reuse the same component such as the Jumbotron on the top of this page and replace it with another topic title, description, image and date in any other pages I want to show by just calling its component's tag name <WeekLayout />.

Does Node.js backend work on GitLab CI/CD?

By setting .gitlab-ci.yaml properly, Node.js backend environment can be installed during the running pipleline. Since the documentation website doesn't need to receive any dynamic data, I made it remain static by adopting Gatsby framework which can produce static React.js website.

There is also an open source package called GitLab Runner which can be used to cooperate with GitLab to run:

  • Multiple jobs concurrently.
  • Use multiple tokens with multiple server (even per-project).
  • Limit number of concurrent jobs per-token.

Here is the explanation about how my static documentation website works during the GitLab pipeline:

# Using the latest node version provided by Docker images to build the React app.
  image: node:latest

# Cache node modules to speed up future builds.
  cache:
    paths:
    - node_modules
    # Enables git-lab CI caching. Both .cache and public must be cached, otherwise builds will fail.
    - .cache/
    - public/

# Name the stages involved in the pipeline.
# Specify the stages. Default stages of a pipeline are: build, test and deploy.
# Order matters.
  stages:
    - deploy

# Job name for gitlab to recognise this results in assets for Gitlab Pages
  pages:
    stage: deploy

# The first two lines are the same scripts that I run locally to build the final bundle files.
    script:
      - npm install       # Install all dependencies.
      - ./node_modules/.bin/gatsby build     # Build for production.

    artifacts:
      paths:
      - public  # The built files in the "public" folder for Gitlab Pages to serve.

    only:
      - master # Only run on master branch.

Web Development: Gatsby + React.js + GraphQL

Setup: Node.js

  1. install Node.js
  2. create folders and js/jsx files as the same structure of the project on GitLab

Install Package Dependencies

  • npm i --save [PACKAGE_NAME] save below packages
"dependencies": {
    "gatsby": "^2.24.91",
    "gatsby-image": "^2.4.19",
    "gatsby-plugin-google-analytics": "^2.3.15",
    "gatsby-plugin-manifest": "^2.4.30",
    "gatsby-plugin-react-helmet": "^3.3.11",
    "gatsby-plugin-sharp": "^2.6.44",
    "gatsby-plugin-sitemap": "^2.4.14",
    "gatsby-plugin-typography": "^2.5.11",
    "gatsby-remark-autolink-headers": "^2.4.0",
    "gatsby-remark-copy-linked-files": "^2.3.19",
    "gatsby-remark-custom-blocks": "^2.3.14",
    "gatsby-remark-embed-video": "^3.0.10",
    "gatsby-remark-external-links": "0.0.4",
    "gatsby-remark-images": "^3.3.40",
    "gatsby-remark-responsive-iframe": "^2.4.17",
    "gatsby-remark-table-of-contents": "^0.1.1",
    "gatsby-source-filesystem": "^2.3.37",
    "gatsby-transformer-remark": "^2.8.47",
    "gatsby-transformer-sharp": "^2.5.15",
    "prop-types": "^15.7.2",
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "react-helmet": "^6.1.0",
    "react-typography": "^0.16.19",
    "reactstrap": "^8.6.0",
    "typography": "^0.16.19"
  }
  • npm i --save-dev [PACKAGE_NAME] save below packages for development purpose
"devDependencies": {
    "env-cmd": "^10.1.0",
    "prettier": "2.1.1"
  }

Add Scripts to package.json for Webpack Bundle

  "scripts": {
    "build": "gatsby build",
    "develop": "env-cmd -f .env gatsby develop",
    "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
    "start": "npm run develop",
    "serve": "gatsby serve",
    "clean": "gatsby clean",
    "test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\" && exit 1"
  }

Edit gatsby-config.js

module.exports = {
  siteMetadata: {
    siteUrl: `https://fabacademy.wantinghsieh.com`,
    title: `Wan-Ting Hsieh 謝宛庭 | Fab Academy 2020`,
    author: `Wan-Ting Hsieh`,
    description:
      "Wan-Ting Hsieh's Project Documentation for Fab Academy 2020. The static site is built by  React.js + Gatsby + GraphQL + GitLab CI."
  },
  plugins: [
    { // for specifying the base directory of source files
      resolve: `gatsby-source-filesystem`,
      options: {
        // name: `static`,
        // path: `${__dirname}/static`,
        name: `src`,
        path: `${__dirname}/src`,
      },
    },
    // for automatically gernerating the sitemap
    `gatsby-plugin-sitemap`,
    {
      resolve: `gatsby-transformer-remark`, // access markdown posts
      options: {
        plugins: [
          {
            resolve: "gatsby-remark-embed-video",
            options: {
              width: 800,
              ratio: 1.77, // Optional: Defaults to 16/9 = 1.77
              height: 400, // Optional: Overrides optional.ratio
              related: false, //Optional: Will remove related videos from the end of an embedded YouTube video.
              noIframeBorder: true, //Optional: Disable insertion of <style> border: 0
              containerClass: 'embedVideo-container' //Optional: Custom CSS class for iframe container, for multiple classes separate them by space
            }
          },
          `gatsby-remark-responsive-iframe`,
          // for copying used files (e.g., videos) in markdown posts to the "public" directory
          `gatsby-remark-copy-linked-files`,
          {
            resolve: `gatsby-remark-images`,
            options: {
              maxWidth: 800, // set image max width in the post
              linkImagesToOriginal: true // disable/enable the external link to the original size of the image
            }
          },
          // for automactically generating the list of table of contents from hearders in markdown posts
          `gatsby-remark-autolink-headers`,
          {
            resolve: `gatsby-remark-table-of-contents`,
            options: {
              exclude: "Table of Contents",
              tight: false,
              fromHeading: 1,
              toHeading: 6,
              className: "table-of-contents"
            },
          },
          { // for opening new tabs by clicking markdown URLs
            resolve: "gatsby-remark-external-links",
            options: {
              target: "_blank",
              rel: "nofollow"
            }
          },
          { // for highlighting markdown code blocks
            resolve: `gatsby-remark-prismjs`,
            options: {
              classPrefix: "language-",
              inlineCodeMarker: null,
              aliases: {},
              showLineNumbers: false,
              noInlineHighlight: false,
              languageExtensions: [
                {
                  language: "superscript",
                  extend: "javascript",
                  definition: {
                    superscript_types: /(SuperType)/,
                  },
                  insertBefore: {
                    function: {
                      superscript_keywords: /(superif|superelse)/,
                    },
                  },
                },
              ],
              prompt: {
                user: "root",
                host: "localhost",
                global: false,
              },
              escapeEntities: {},
            },
          },
          { // for adding custom block css classes
            resolve: "gatsby-remark-custom-blocks",
            options: {
              blocks: {
                gifSize: {
                  classes: "gifSize",
                },
              },
            },
          }
        ]
      }
    },
    { // for tracking the number of website views
      resolve: `gatsby-plugin-google-analytics`,
      options: {
        trackingId: "UA-99560507-5",
      },
    },
    // for adding CSS objects in .jsx files
    `gatsby-plugin-emotion`,
    { // for customizing website font style
      resolve: `gatsby-plugin-typography`,
      options: {
        pathToConfigModule: `src/utils/typography`,
      },
    },
    // for editing page metadata objects in React.js pages
    `gatsby-plugin-react-helmet`,
    // for automatically optimizing images in React.js pages
    `gatsby-plugin-sharp`,
    `gatsby-transformer-sharp`,
    { // for editing favicon, name amd browser background color
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `Fab Academy 2020`,
        short_name: `Fab Academy 2020`,
        start_url: `/`,
        background_color: `#001133`,
        theme_color: `#001133`,
        display: `standalone`,
        icon: `static/favicon.png`, // This path is relative to the root of the site.
      },
    },
    { // for visiting the website offline
      resolve: 'gatsby-plugin-offline',
      options: {
         workboxConfig: {
            globPatterns: ['**/static*']
         }
      }
   }
  ],
}

Create & Edit gatsby-node.js

const path = require('path');

// Goal 1: Generate a slug for each post: postName.md -> postname -> /blog/postName
// Goal 2: Generate the blog post page template
// Goal 3: Generate a new page for each post
module.exports.onCreateNode = ({ node, actions }) => {
    const { createNodeField } = actions;

    if (node.internal.type == 'MarkdownRemark') {
        // reduce the link: https://nodejs.org/dist/latest-v14.x/docs/api/path.html#path_path_basename_path_ext
        const slug = path.basename(node.fileAbsolutePath, '.md');

        createNodeField({
            node,
            name: 'slug',
            value: slug
        });
    }
}

// 1. Get path to template
// 2. Get markdown data
// 3. Create new pages
module.exports.createPages = async ({ graphql, actions }) => {
    const { createPage } = actions;
    const weekTemplate = path.resolve('./src/templates/weekLayout.jsx');
    const res = await graphql(`
        query {
            allMarkdownRemark {
                edges {
                    node {
                        fields {
                            slug
                        }
                    }
                }
            }
        }
    `);

    res.data.allMarkdownRemark.edges.forEach((edge) => {
        createPage({
            component: weekTemplate,
            path: `/assignment/${edge.node.fields.slug}`,
            context: {
                slug: edge.node.fields.slug
            }
        });
    });
}

Create & Edit gatsby-browser.js (for code highlighting)

require("prismjs/themes/prism-okaidia.css")
require("prismjs/plugins/line-numbers/prism-line-numbers.css")
require("prismjs/plugins/command-line/prism-command-line.css")

Build or Start Local Gatsby Website

  • npm run build build source files in public folder
  • npm run develop locally start and listen to the website on port 8000 (http://localhost:8000/)

Image optimization is about reducing the file size of the images displayed on a website as much as possible without sacrificing quality so that the load times of a web page remain low. It’s also about image SEO. That is, getting project images and decorative images to rank on Google and other image search engines.

Image Optimization

Tool 1: Photoshop

Since I am quite familiar with using Photoshop to process images for printing or design purpose. So I choose it for my initial image optimization. It is intuitive to adjust the scale of an image by inputing the width, height or resolution you want to the image size dialogue box in Photoshop.

photoshop

Tool 2: TinyPNG

After adjusting the width, height and resolution of my images in Photoshop, I then upload the images to a online image compression tool TinyPNG which uses smart lossy compression techniques to reduce the imge size without sacrificing quality of the images.

TinyPNG

Here is the reducing percentage of the images used for this week's documentation:

TinyPNG-reduce

Check Tool: Google PageSpeed Insights

Google PageSpeed Insights is a handy tool to check which parts of your website slow down the performance and give some advice about how to fix it including image compression.

PageSpeed-1

Here is the testing result of my fab academy website:

PageSpeed-2 PageSpeed-3 PageSpeed-4

Creative Commons License
© Copyright 2020 | Created by Wan-Ting Hsieh | All Rights Reserved