github builds personal dynamic blog at zero cost

Posted by NightFalcon90909 on Thu, 16 Dec 2021 11:13:05 +0100

Reading guide

This paper aims to record the whole construction process, problems and solutions, hoping to bring you some help.

The main technologies involved in this paper are as follows:

Vue3.0 - Composition API
GraphQL
ESLint
Semantic Versioning,Commitzion,etc```

ISSUE is used to record articles. Markdown is naturally supported. The interface calls Github Api and is deployed on Github Pages. There is no additional cost other than one-time development.

Obviously, the revision of this blog is based on the third way. Next, let's start from 0 step by step.

Technology selection
As it is a personal blog, technology selection can be a bold attempt.

The author chooses Vue cli to initialize the project structure, and uses Vue 3 X syntax composition API for page development. Use Github API v4, that is, GraphQL syntax for API calls.

Start development
Environmental preparation
node
Go to node JS official website. It is recommended to download the stable version of LTS. After downloading, follow the steps to install.

Remember to select Add To Path under Window to ensure that global commands are available

vue-cli
Execute the following code to install globally.

npm install -g @vue/cli
Project initialization
Initialize the project through Vue cli, select according to the following contents or select on demand.

vue create my-blog
init

After initializing and installing dependencies, the project directory you can view is as follows:

dir

Other dependent installations
@vue/composition-api
Necessary dependencies using Vue 3.0 syntax

npm install @vue/composition-api --save
graphql-request
Simple and lightweight graphQL client. Similarly, Apollo, Relay, etc. can be selected. The reason for choosing it is: simple and lightweight, and based on Promise.

npm install graphql-request --save
github-markdown-css
Render Markdown in Github's style. The reason for choosing it is original.

npm install github-markdown-css --save
Project development
My blog used fexo style theme before, so this time it is also developed based on this UI.

The whole project is divided into several pages:

/archives article list
/archives/:id article details
/labels list
/links friend chain
/about
/Board message board
/search Search
Ⅰ. Request encapsulation

import { GraphQLClient } from 'graphql-request';

import config from '../../config/config';
import Loading from '../components/loading/loading';

const endpoint = 'https://api.github.com/graphql';

const graphQLClient = new GraphQLClient(endpoint, {
  headers: {
    authorization: `bearer ${config.tokenA}${config.tokenB}`,
    'X-Requested-With': 'XMLHttpRequest',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
  },
});

const Http = (query = {}, variables = {}, alive = false) => new Promise((resolve, reject) => {
  graphQLClient.request(query, variables).then((res) => {
    if (!alive) {
      Loading.hide();
    }
    resolve(res);
  }).catch((error) => {
    Loading.hide();
    reject(error);
  });
});

export default Http;

We can see that headers are configured. Here is the authentication required by Github Api.

There are two pits, which can only be found after the code is packaged and submitted:

The token cannot be directly submitted to Github, otherwise it will be found invalid when used again. Here I guess it's a security scanning mechanism, so I split the token into two parts to bypass this.

The content type needs to be set to x-www-form-urlencoded, otherwise the cross domain request will fail.

Next, we will modify main JS file to mount the request method to the Vue instance.

import Vue from 'vue';
import Http from './api/api';

Vue.prototype.$http = Http;

Ⅱ. Article list development

We will mainly introduce the composition API and graphqh. For the rest, please refer to the Vue documentation

First, we need to introduce the composition API and modify main JS file

import Vue from 'vue';
import VueCompositionApi from '@vue/composition-api';

Vue.use(VueCompositionApi);

Then create a new archives Vue to undertake page content.

The first change is the change of the life cycle. The setup function is used to replace the previous beforeCreate and created hooks. There are two points worth noting:

When used with Templates, this function returns a data object for template.
There is no this object in this function, so you need to use context Root gets the root instance object.

export default {
  setup (props, context) {
    // Through context Root obtains the root instance and finds the request method previously mounted on the Vue instance
    context.root.$http(xxx)
  }
}

Refer to Github Api for syntax of data query.

I use the issue of blog warehouse as the article here, so the query syntax here roughly means:

Check the warehouse according to owner and name. Owner is the Github account and name is the warehouse name.
Query the issues article list and return it in reverse order according to the creation time. first indicates how many articles are returned each time. after means where to start. Therefore, it is easy to realize paging in combination with this. The code is as follows:

// Introduce, use reactive to create responsive objects
import {
  reactive,
} from '@vue/composition-api';
export default {
  setup (props, context) {
    const archives = reactive({
      cursor: null
    });
    const query = `query {
      repository(owner: "ChenJiaH", name: "blog") {
        issues(orderBy: {field: CREATED_AT, direction: DESC}, labels: null, first: 10, after:${archives.cursor}) {
          nodes {
            title
            createdAt
            number
            comments(first: null) {
              totalCount
            }
          }
          pageInfo {
            endCursor
            hasNextPage
          }
        }
      }
    }`;
    // Through context Root obtains the root instance and finds the request method previously mounted on the Vue instance
    context.root.$http(query).then(res => {
      const { nodes, pageInfo } = res.repository.issues
      archives.cursor = pageInfo.endCursor  // Identification of the last item
    })
  }
}

Ⅲ. Tag list development

Here I can't find all the labels data returned in issues, so I can only query all labels first, and then query the first label by default. The syntax is as follows:

const getData = () => {
    const query = `query {
        repository(owner: "ChenJiaH", name: "blog") {
          issues(filterBy: {labels: ${archives.label}}, orderBy: {field: CREATED_AT, direction: DESC}, labels: null, first: 10, after: ${archives.cursor}) {
            nodes {
              title
              createdAt
              number
              comments(first: null) {
                totalCount
              }
            }
            pageInfo {
              endCursor
              hasNextPage
            }
            totalCount
          }
        }
      }`;
    context.root.$http(query).then((res) => {
      ```
    });
  };
  const getLabels = () => {
    context.root.$loading.show('Try to query for you');
    const query = `query {
      repository(owner: "ChenJiaH", name: "blog") {
        labels(first: 100) {
          nodes {
            name
          }
        }
      }
    }`;
    context.root.$http(query).then((res) => {
      archives.loading = false;
      archives.labels = res.repository.labels.nodes;

      if (archives.labels.length) {
        archives.label = archives.labels[0].name;

        getData();
      }
    });
  };

Ⅳ. Article details development

The article details are divided into two parts: Article Details query and article comments.

Article details query
Here, first introduce the style file of github markdown CSS, and then add the style name of markdown body to the markdown container, which will be automatically rendered into github style.

<template>
    <div class="markdown-body">
      <p class="cont" v-html="issue.bodyHTML"></p>
    </div>
</template>
<script>
import {
  reactive,
  onMounted,
} from '@vue/composition-api';
import { isLightColor, formatTime } from '../utils/utils';

export default {
    const { id } = context.root.$route.params;  // Get issue id
    const getData = () => {
      context.root.$loading.show('Try to query for you');
      const query = `query {
          repository(owner: "ChenJiaH", name: "blog") {
            issue(number: ${id}) {
              title
              bodyHTML
              labels (first: 10) {
                nodes {
                  name
                  color
                }
              }
            }
          }
        }`;
      context.root.$http(query).then((res) => {
        const { title, bodyHTML, labels } = res.repository.issue;
        issue.title = title;
        issue.bodyHTML = bodyHTML;
        issue.labels = labels.nodes;
      });
    };
};
</script>
<style lang="scss" scoped>
  @import "~github-markdown-css";
</style>

Note that there is a label color acquisition

As we all know, the font color of Github Label is automatically adjusted according to the background color, so I encapsulate a method to determine whether it is a bright color to set the text color.

// isLightColor
const isLightColor = (hex) => {
const rgb = [parseInt(0x${hex.substr(0, 2)}, 16), parseInt(0x${hex.substr(2, 2)}, 16), parseInt(0x${hex.substr(4, 2)}, 16)];
const darkness = 1 - (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]) / 255;
return darkness < 0.5;
};
Article comments
I use utterances here. Please follow the steps to initialize the project. For Blog Post, please select Specific issue number, so that the comments will be based on the issue, that is, the current article. Then configure your related information import in the following way on the page:

import {
  reactive,
  onMounted,
} from '@vue/composition-api';
export default {
  setup(props, context) {
    const { id } = context.root.$route.params;  // issue id
    const initComment = () => {
      const utterances = document.createElement('script');
      utterances.type = 'text/javascript';
      utterances.async = true;
      utterances.setAttribute('issue-number', id);
      utterances.setAttribute('theme', 'github-light');
      utterances.setAttribute('repo', 'ChenJiaH/blog');
      utterances.crossorigin = 'anonymous';
      utterances.src = 'https://utteranc.es/client.js';

      // Find the corresponding container insert. I use comment here
      document.getElementById('comment').appendChild(utterances);
    };

    onMounted(() => {
      initComment();
    });    
  }
}

The advantage of this scheme is that the data is completely from Github Issue and comes with its own login system, which is very convenient.

Ⅴ. Message board development

The above part just mentioned utterances. We can develop the message board based on this. We only need to replace the Blog Post with other methods. I choose Issue term to leave a message under a single Issue with a custom title. In order to avoid distinguishing from articles, I use another warehouse to manage messages. The implementation code is as follows:

import {
  onMounted,
} from '@vue/composition-api';

export default {
  setup(props, context) {
    context.root.$loading.show('Try to query for you');

    const initBoard = () => {
      const utterances = document.createElement('script');
      utterances.type = 'text/javascript';
      utterances.async = true;
      utterances.setAttribute('issue-term', '[[message board]');
      utterances.setAttribute('label', ':speech_balloon:');
      utterances.setAttribute('theme', 'github-light');
      utterances.setAttribute('repo', 'ChenJiaH/chenjiah.github.io');
      utterances.crossorigin = 'anonymous';
      utterances.src = 'https://utteranc.es/client.js';

      document.getElementById('board').appendChild(utterances);

      utterances.onload = () => {
        context.root.$loading.hide();
      };
    };

    onMounted(() => {
      initBoard();
    });
  },
};

Ⅵ. Search page development

I ran into a pit here. I haven't found the query syntax corresponding to fuzzy search for a long time.

Thanks to simbawus, which solves the problem of query syntax. Specific queries are as follows:

      const query = `query {
        search(query: "${search.value} repo:ChenJiaH/blog", type: ISSUE, first: 10, after: ${archives.cursor}) {
          issueCount
          pageInfo {
            endCursor
            hasNextPage
          }
          nodes {
            ```on Issue {
              title
              bodyText
              number
            }
          }
        }
      }`;

Fortunately, there is an expansion operator, otherwise the parsing format in nodes doesn't know how to write it.

Ⅶ. Other page development
Most of the other pages are static pages, so they can be developed according to the relevant syntax documents without any special difficulties.

In addition, I did not use all the syntax of the composition API, but made a basic attempt according to the needs of the project.

Project release and deployment
Project submission
Commit is adopted for the project submission. The reason is that the submission format is standardized, the change log can be generated quickly, and it can be automated in the later stage. Refer to the corresponding use steps.

Project version management
The version management of the project adopts semantic versioning 2.0 0

Project deployment
Write a deploy SH script and configure it to package JSON. Executing npm run deploy will automatically package and push it to the GH pages branch for page update.

// package.json
{

"scripts": {
  "serve": "vue-cli-service serve",
  "build": "vue-cli-service build",
  "lint": "vue-cli-service lint",
  "inspect": "vue-cli-service inspect",
  "deploy": "sh build/deploy.sh",
  "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md"
},

}
#!/usr/bin/env sh

set -e

npm run build

cd dist

git init
git config user.name 'McChen'
git config user.email 'chenjiahao.xyz@gmail.com'
git add -A
git commit -m 'deploy'

git push -f git@github.com:ChenJiaH/blog.git master:gh-pages

cd -
To use GH pages, you need to create a user name first github.io's warehouse

ending
So far, a 0-cost dynamic blog has been completely built. During the development process, we also encountered some eslint related prompts and errors, which can be basically solved by direct search.

Topics: Javascript github Vue.js