It was first published on wechat public account “Front-end Growth Record”, written on October 12, 2019

takeaway

As the old saying goes, a bad memory is better than a bad pen. There are always some things in life that you want to write down.

This article aims to record the whole process of building and problems encountered and solutions, hoping to bring some help to you.

The main technologies involved in this paper:

  • Vue3.0 – Composition API
  • GraphQL
  • ESLint
  • Semantic Versioning,Commitzion,etc…

online

My blog

background

The history of my blog has been divided into three phases:

  • Build static blog based on Hexo and provide domain name and server resources combined with Github Pages

  • Purchase server and domain name by oneself, develop and deploy page and interface, build dynamic blog

  • Build dynamic blog based on Github Pages and Github Api

In the first way, article content is written in Markdown and static Pages are generated by Hexo and deployed on Github Pages. The downside is that you need to recompile and deploy every time you have something new.

The second approach is extremely flexible and can be developed on demand. The disadvantages are also obvious, development and maintenance of a lot of work, as well as server and domain name costs.

The third way is to use ISSUE to record articles, which naturally supports Markdown. The interface calls the Github Api and is deployed on Github Pages. There is no additional cost beyond one-time development.

Obviously, this blog revision is based on the third way to achieve, next we start from zero step by step.

Technology selection

As a personal blog, the selection of technology can be bold.

The author chose VUE-CLI to initialize the project structure, and used the syntax Composition-Api of VUe3. X to develop the page. Github API V4, also known as GraphQL syntax, is used for API calls.

To fit the development of

Environment to prepare

node

Download the stable VERSION of LTS from node. js. After downloading it, follow the steps to install it.

In Windows, select Add To Path To ensure the global command is available

vue-cli

Perform the following code for a global installation.

npm install -g @vue/cli
Copy the code

Project initialization

To initialize the project via vue-CLI, choose as follows or choose as needed.

vue create my-blog
Copy the code

After initializing and installing dependencies, you can view the following project directory:

Other dependent installations

  1. @vue/composition-api

Necessary dependencies to use Vue 3.0 syntax

npm install @vue/composition-api --save
Copy the code
  1. graphql-request

Simple and lightweight graphQL client. Apollo, Relay and so on can be selected. It was chosen for its simplicity and Promise.

npm install graphql-request --save
Copy the code
  1. github-markdown-css

Use Github’s style to render Markdown, which was chosen for its authenticity.

npm install github-markdown-css --save
Copy the code

Project development

My blog used a FexO style theme before, so this time I developed it as a UI.

The project is divided into several pages:

  • / Archives articles list
  • /archives/:id article details
  • / Labels list
  • / links chain
  • / about about
  • / board, message board
  • / search search

ⅰ. Request encapsulation

View the source code

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;
Copy the code

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

There are two potholes that were discovered only after the submission code was packaged:

  1. The token cannot be submitted to Github directly. Otherwise, the token will be invalid. Here I guess it is the security scanning mechanism, so I split the token into two parts above to get around this.

  2. Content-type needs to be x-www-form-urlencoded, otherwise cross-domain requests will fail.

Next we’ll modify the main.js file to mount the request method to the Vue instance.

. import Vuefrom 'vue';
import Http from './api/api'; Vue.prototype.$http = Http; .Copy the code

ⅱ. Article list development

View the source code

I’ll focus on the introduction to comic-API and Graphqh, and refer to the Vue documentation for the rest

We first need to introduce composition-API and modify the main.js file

. import Vuefrom 'vue';
import VueCompositionApi from '@vue/composition-api'; Vue.use(VueCompositionApi); .Copy the code

Then create an Archives. Vue to take over the page content.

The first change is the lifecycle change, using the setup function instead of the previous beforeCreate and Created hooks. There are two things worth noting:

  1. The function andTemplatesReturns a to when used togethertemplateThe data object used.
  2. There’s nothing in this functionthisObject, requiredcontext.rootGet the root instance object.
. exportdefault {
  setup (props, context) {
    // Get the root instance through context.root and find the request method that was previously mounted on the Vue instance
    context.root.$http(xxx)
  }
}
...
Copy the code

Refer to the Github Api for data query syntax.

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

Query the warehouse according to owner and name. Owner is the Github account and name is the warehouse name. Query the list of issues articles and return them in reverse order by creation time. First indicates the number of returned articles each time. After indicates where to start the search. So combining this makes it easy to implement paging as follows:

.// Use Reactive to create reactive 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
          }
        }
      }
    }`;
    // Get the root instance through context.root and find the request method that was previously mounted on the Vue instance
    context.root.$http(query).then(res= > {
      const { nodes, pageInfo } = res.repository.issues
      archives.cursor = `"${pageInfo.endCursor}"`  // The last identifier needs to be passed in as "" on the package}}})...Copy the code

ⅲ. Tag list development

View the source code

Here, I did not find all labels data returned in issues, so I had to check all labels first and then query the first label by default. The syntax is as follows:

. const getData =(a)= > {
    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 = (a)= > {
    context.root.$loading.show('Try to inquire 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(); }}); }; .Copy the code

ⅳ. Detailed development of the article

View the source code

Article details are divided into two parts: article details inquiry and article comments.

  1. Article Details Enquiry

Here we first introduce the github markdown-CSS style file, and then add the markDown-body style name to the MarkDown container. The internal style will be automatically rendered to 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> ...Copy the code

Notice there’s a label color fetch

As we all know, the font color of Github Label is automatically adjusted according to the background color, so I have packaged 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;
};
Copy the code
  1. Article comment section

Here I used Utterances, please initialize the project according to the steps, please select Specific issue number for Blog Post, so that the comment will be based on the issue, that is, the current article. Then configure your relevant information import in the page as follows:

. import { reactive, onMounted, }from '@vue/composition-api';
export default {
  setup(props, context) {
    const { id } = context.root.$route.params;  // issue id
    const initComment = (a)= > {
      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 to insert, I use comment here
      document.getElementById('comment').appendChild(utterances);
    };

    onMounted((a)= >{ initComment(); }); }}...Copy the code

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

ⅴ. Message board development

View the source code

It happened that utterances was mentioned in the above part. It is convenient to develop the message board based on this, so it is only necessary to change the Blog Post into other ways. Here I choose “issue-term”, leaving a message under a single issue with a self-defined title. To avoid being separated from the articles, I use a separate repository 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 inquire for you');

    const initBoard = (a)= > {
      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 = (a)= > {
        context.root.$loading.hide();
      };
    };

    onMounted((a)= >{ initBoard(); }); }}; .Copy the code

ⅵ. Search page development

View the source code

Here encountered a pit, looking for a long time did not find fuzzy search corresponding query syntax.

Thanks to Simbawus for solving the query syntax problem. 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
            }
          }
        }
      }`; .Copy the code

Also good… Expand the operator, otherwise the parse format of nodes here does not know how to write.

ⅶ. Other page development

Most of the other pages are static pages, so follow the relevant syntax document development, there is no special difficulty.

Also, I didn’t use the entire syntax of commination-API, just a basic experiment based on the needs of the project.

Project release and deployment

Project submission

Commitizen is used for project submission. The reason is that the submission format is standardized, change logs can be generated quickly, etc., and can be automated later. Refer to the corresponding operation procedure to use it.

Versioning of projects

The project is versioned using Semantic Versioning 2.0.0

Project deployment

A deploy.sh script is written and configured to package.json. Executing NPM run deploy will automatically package and push to the GH-Pages branch for page updates.

// 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"
  },
 ...
}
Copy the code
#! /usr/bin/env shset -e npm run build cd dist git init git config user.name 'McChen' git config user.email '[email protected]' git  add -A git commit -m 'deploy' git push -f [email protected]:ChenJiaH/blog.git master:gh-pages cd -Copy the code

To use gh-pages, you need to create a repository with the user name.github

At the end

At this point, a 0 – cost dynamic blog has been completely built. Some esLint related prompts and errors were encountered during the development process, which can be solved by direct search.

If you have any questions or are wrong, please leave a message.

(after)


This article is an original article and may update knowledge points and correct errors. Therefore, please keep the original source for easy traceability and avoid misleading old erroneous knowledge. At the same time, you can have a better reading experience.