<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Chanvin Xiao</title>
    <description>The latest articles on Forem by Chanvin Xiao (@chanvin).</description>
    <link>https://forem.com/chanvin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F379719%2F60d1a9f0-29e2-43f2-9b24-42df4dc274c0.png</url>
      <title>Forem: Chanvin Xiao</title>
      <link>https://forem.com/chanvin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/chanvin"/>
    <language>en</language>
    <item>
      <title>Some Caveats for GitHub Workflow and Action</title>
      <dc:creator>Chanvin Xiao</dc:creator>
      <pubDate>Sun, 29 Dec 2024 12:33:24 +0000</pubDate>
      <link>https://forem.com/chanvin/some-caveats-for-github-workflow-and-action-1kjg</link>
      <guid>https://forem.com/chanvin/some-caveats-for-github-workflow-and-action-1kjg</guid>
      <description>&lt;p&gt;There are some caveats for GitHub workflow and action, including yaml configuration of workflow, script composition of action, and the protection setting for corresponding branch, summarize as following for reference&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  on.issues.types
&lt;/h3&gt;

&lt;p&gt;If it's necessary to check label, &lt;code&gt;opened&lt;/code&gt; is not needed, set &lt;code&gt;labeled&lt;/code&gt; is enough, since even if the label is set when the issue is created, &lt;code&gt;labeled&lt;/code&gt; will be triggered&lt;/p&gt;

&lt;h3&gt;
  
  
  permissions
&lt;/h3&gt;

&lt;p&gt;If it's necessary to checkout current repo, &lt;code&gt;contents: write&lt;/code&gt; is needed, or else there will be permission issue&lt;/p&gt;

&lt;h3&gt;
  
  
  jobs.check.steps.env
&lt;/h3&gt;

&lt;p&gt;If it's necessary to utilize GitHub API, &lt;code&gt;GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}&lt;/code&gt; is needed, as environmental variable&lt;/p&gt;

&lt;h2&gt;
  
  
  Action
&lt;/h2&gt;

&lt;h3&gt;
  
  
  List repository issues
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#list-repository-issues" rel="noopener noreferrer"&gt;API&lt;/a&gt; no only return issues, but also return prs, with default 30 records per page, &lt;code&gt;labels&lt;/code&gt; can be assign to filter&lt;/p&gt;

&lt;h3&gt;
  
  
  List pull requests
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-pull-requests" rel="noopener noreferrer"&gt;API&lt;/a&gt; return all the pull requests, with default 30 records per page, it's possible to fulfill pagination via &lt;code&gt;per_page&lt;/code&gt; and &lt;code&gt;page&lt;/code&gt;, such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const prs = [];
for (let page = 1; ; page++) {
  const { data } = await octokit.rest.pulls.list({
    ...context.repo,
    base: `refs/heads/main`,
    state: 'open',
    per_page: PER_PAGE,
    page,
  });
  prs.push(...data);
  if (data.length &amp;lt; PER_PAGE) {
    break;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Get commit status
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#get-the-combined-status-for-a-specific-reference" rel="noopener noreferrer"&gt;API&lt;/a&gt; list state of all the contexts, it's possible to filter current context&lt;/p&gt;

&lt;h3&gt;
  
  
  Create commit status
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#create-a-commit-status" rel="noopener noreferrer"&gt;API&lt;/a&gt;，指定 &lt;code&gt;state&lt;/code&gt;，&lt;code&gt;context&lt;/code&gt; 和 &lt;code&gt;description&lt;/code&gt;（作为运行结果显示）&lt;br&gt;
&lt;a href="https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#create-a-commit-status" rel="noopener noreferrer"&gt;API&lt;/a&gt; is used to create commit status, it's possible to specify &lt;code&gt;state&lt;/code&gt;，&lt;code&gt;context&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; (shown as running result)&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Require status checks
&lt;/h3&gt;

&lt;p&gt;It's necessary to check &lt;em&gt;Require status checks to pass before merging&lt;/em&gt; under &lt;em&gt;Protect matching branches&lt;/em&gt; in &lt;em&gt;Branch protection rule&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add required checks
&lt;/h3&gt;

&lt;p&gt;It's necessary to add current GitHub action to Status checks that are required&lt;/p&gt;

</description>
      <category>github</category>
    </item>
    <item>
      <title>Transform Class React Components to Functional</title>
      <dc:creator>Chanvin Xiao</dc:creator>
      <pubDate>Sat, 30 Dec 2023 11:06:12 +0000</pubDate>
      <link>https://forem.com/chanvin/transform-class-react-components-to-functional-3o2p</link>
      <guid>https://forem.com/chanvin/transform-class-react-components-to-functional-3o2p</guid>
      <description>&lt;p&gt;As functional React components is more modern with support to hooks which is useful, it's common practice to rewrite the old styled class components to functional components. This article summarizes some common steps and pitfalls for the transformation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Replacements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Definition
&lt;/h3&gt;

&lt;p&gt;From&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class (\w+) extends Component \{
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const $1: FC = () =&amp;gt; {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;It's for situation without &lt;code&gt;export&lt;/code&gt; and &lt;code&gt;props&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(export) default class (\w+) extends Component \{
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$1 const $2: FC&amp;lt;$2Props&amp;gt; = () =&amp;gt; {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;As the second captured word, &lt;code&gt;$2&lt;/code&gt; is the component name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$2Props&lt;/code&gt; should be defined as the name of &lt;code&gt;props&lt;/code&gt; interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Attributes Prefix
&lt;/h3&gt;

&lt;p&gt;From&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;this\.(state\.|props\.)?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Assume that &lt;code&gt;props&lt;/code&gt; is destructed uniformly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Lift Cycle functions
&lt;/h3&gt;

&lt;p&gt;From&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;componentDidMount() { 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;useEffect(() =&amp;gt; {}, []);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;componentDidUpdate&lt;/code&gt; can also be transformed to &lt;code&gt;useEffect&lt;/code&gt;, with appropriate dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;componentWillUnmount&lt;/code&gt; can be transformed to return function of corresponding &lt;code&gt;useEffect&lt;/code&gt; handler.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  State Related Statement
&lt;/h3&gt;

&lt;p&gt;From&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;state = {
  data: null,
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [data, setData] = useState();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;this.setState({
  data,
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;setData(data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Class Methods
&lt;/h3&gt;

&lt;p&gt;From&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^(\s*)(\w+)\((\w*)\) \{
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$1const $2 = ($3) =&amp;gt; {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;It's for regular function definition.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$1&lt;/code&gt; is white spaces, &lt;code&gt;$2&lt;/code&gt; is method name, &lt;code&gt;$3&lt;/code&gt; is parameters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^(\s*)((\w+) = (async )?\((\w+(, )?)*\) =&amp;gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$1const $2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;It's for arrow function definition.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$1&lt;/code&gt; is white spaces, &lt;code&gt;$2&lt;/code&gt; is everything from the method name.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Class Getter
&lt;/h3&gt;

&lt;p&gt;From&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^(\s*)(get) (\w+)\(\)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$1const $2\u$3 = () =&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;\u&lt;/code&gt; means capitalizing the following captured word.&lt;/li&gt;
&lt;li&gt;The invoking of the getter should have &lt;code&gt;()&lt;/code&gt; after the name.&lt;/li&gt;
&lt;li&gt;If the getter is simple, it can be assign directly without function.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Render function
&lt;/h3&gt;

&lt;p&gt;From&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;render() {
  return (
    &amp;lt;&amp;gt;&amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return (
  &amp;lt;&amp;gt;&amp;lt;/&amp;gt;
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Notable Pitfalls
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Name Conflict
&lt;/h3&gt;

&lt;p&gt;It's possible for class component to have the same name as &lt;code&gt;attributes&lt;/code&gt; and &lt;code&gt;props&lt;/code&gt;, such as &lt;code&gt;this.data&lt;/code&gt; and &lt;code&gt;this.props.data&lt;/code&gt;.&lt;br&gt;
With &lt;code&gt;this.data&lt;/code&gt; become &lt;code&gt;data&lt;/code&gt;, and it's common to destruct &lt;code&gt;props&lt;/code&gt; as &lt;code&gt;const {data} = props&lt;/code&gt;, the name conflict comes.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Callback
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;this.setState&lt;/code&gt;, we can set a callback to be invoked after the state is changed actually, but we have to change this implementation to &lt;code&gt;useEffect&lt;/code&gt; with the changed state as dependency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Function as State
&lt;/h3&gt;

&lt;p&gt;If the value of a state is a function, you wrap it into an anonymous function or else the hook version of &lt;code&gt;setState&lt;/code&gt; will treat it as a callback.&lt;br&gt;
Actually, in most case, this kind of state is not related to rendering, so perhaps &lt;code&gt;useRef&lt;/code&gt; is more suitable for them.&lt;/p&gt;

&lt;p&gt;This article shows some replacement with RegExp, which will make the transformation from class to functional component easier, and some pitfalls that you may encounter during the process for you to pay attention to, but to be sure, there can be more jobs for different scenarios.&lt;/p&gt;

</description>
      <category>react</category>
      <category>functionalreactiveprogramming</category>
      <category>regex</category>
    </item>
    <item>
      <title>Common Git Command Line Operation</title>
      <dc:creator>Chanvin Xiao</dc:creator>
      <pubDate>Sat, 31 Dec 2022 11:07:24 +0000</pubDate>
      <link>https://forem.com/chanvin/common-git-command-line-operation-56c2</link>
      <guid>https://forem.com/chanvin/common-git-command-line-operation-56c2</guid>
      <description>&lt;p&gt;This article record the specific usage method of some common git command line operation&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://git-scm.com/docs/git-clone"&gt;git clone&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git clone REPOSITORY_URL&lt;/code&gt;&lt;br&gt;
Clone repository, and use the name of repository as local folder name&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git clone REPOSITORY_URL FOLDER&lt;/code&gt;&lt;br&gt;
Clone repository, and use FOLDER as local folder name&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://git-scm.com/docs/git-fetch"&gt;git fetch&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git fetch origin&lt;/code&gt;&lt;br&gt;
Update all the remote branch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git fetch origin BRACH&lt;/code&gt;&lt;br&gt;
Update designated remote branch &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://git-scm.com/docs/git-pull"&gt;git pull&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git pull origin&lt;/code&gt;&lt;br&gt;
Equivalent to &lt;code&gt;fetch&lt;/code&gt; + &lt;code&gt;merge&lt;/code&gt; coresponding upstream branch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git pull origin BRACH&lt;/code&gt;&lt;br&gt;
Pull designated branch to current branch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git pull origin --rebase master&lt;/code&gt;&lt;br&gt;
Make local branch rebase remote master branch&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://git-scm.com/docs/git-push"&gt;git push&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git push origin&lt;/code&gt;&lt;br&gt;
Push branch to coresponding remote upstream branch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git push origin BRANCH&lt;/code&gt;&lt;br&gt;
Push branch to remote designated branch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git push --set-upstream origin BRANCH&lt;/code&gt;&lt;br&gt;
Push branch to remote designated branch, and make it as upstream branch (generally need to be used for pushing branch of your own for the first time)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git push -f origin&lt;/code&gt;&lt;br&gt;
Force push branch to corresponding remote upstream branch (will override remote branch, need to be used carefully)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git push origin -d BRANCH&lt;/code&gt;&lt;br&gt;
Delete remote branch&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://git-scm.com/docs/git-branch"&gt;git branch&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git branch&lt;/code&gt;&lt;br&gt;
List all local branch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git branch -a&lt;/code&gt;&lt;br&gt;
List all local and remote branch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git branch -m NEW_BRANCH&lt;/code&gt;&lt;br&gt;
Rename current branch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git branch -d BRANCH&lt;/code&gt;&lt;br&gt;
Delete merged branch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git branch -D BRANCH&lt;/code&gt;&lt;br&gt;
Force delete branch (even if not be merged yet)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://git-scm.com/docs/git-checkout"&gt;git checkout&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git checkout BRANCH&lt;/code&gt;&lt;br&gt;
Switch to designated branch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git checkout -b NEW_BRANCH&lt;/code&gt;&lt;br&gt;
Create new branch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git checkout -b NEW_BRANCH BRANCH&lt;/code&gt;&lt;br&gt;
Create new branch based on BRANCH&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git checkout SHA-1&lt;/code&gt;&lt;br&gt;
Switch to a commit, or use HEAD~N (N as 1, 2, 3...) to switch to previous Nth commit&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git checkout SHA-1 /PATH/TO/FILE&lt;/code&gt;&lt;br&gt;
Restore file to designated commit version&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git checkout --theirs /PATH/TO/FILE&lt;/code&gt;&lt;br&gt;
Use theirs' file version in case of conflict&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git checkout --ours /PATH/TO/FILE&lt;/code&gt;&lt;br&gt;
Use ours' file version in case of conflict&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git checkout -&lt;/code&gt;&lt;br&gt;
Switch to previous branch, suitable for switching frequently between two branches&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://git-scm.com/docs/git-add"&gt;git add&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git add .&lt;/code&gt;&lt;br&gt;
Mark all added / modified / deleted files as to-be-committed&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git add /PATH/TO/FILE&lt;/code&gt;&lt;br&gt;
Mark a single file as to-be-committed, suitable for situation when there're other modified files which don't need to be committed&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://git-scm.com/docs/git-commit"&gt;git commit&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git commit&lt;/code&gt;&lt;br&gt;
Commit files marked by &lt;code&gt;git add&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git commit -a&lt;/code&gt;&lt;br&gt;
Commit modified / deleted files (if there's newly added file, need to use &lt;code&gt;git add&lt;/code&gt; to mark firstly)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git commit -am "MESSAGE"&lt;/code&gt;&lt;br&gt;
Commit modified / deleted files and assign comment (suitable for temporary or simple comment content)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git commit --amend&lt;/code&gt;&lt;br&gt;
Update last commit, can add &lt;code&gt;-a&lt;/code&gt; or run &lt;code&gt;git add&lt;/code&gt; firstly to append updated files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git commit --amend --reset-author&lt;/code&gt;&lt;br&gt;
Default updating commit won't change author, can explicitly config if necessary&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://git-scm.com/docs/git-cherry-pick"&gt;git cherry-pick&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git cherry-pick SHA-1&lt;/code&gt;
Apply a commit to current branch&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://git-scm.com/docs/git-status"&gt;git status&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git status&lt;/code&gt;
Check current status&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://git-scm.com/docs/git-diff"&gt;git diff&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git diff&lt;/code&gt;&lt;br&gt;
Updating contents of current modified files which has not been marked as to-be-committed&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git diff --cache&lt;/code&gt;&lt;br&gt;
Updating contents of current modified files which has been marked as to-be-committed&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git diff /PATH/TO/FILE&lt;/code&gt;&lt;br&gt;
Updating contents of designated file, and can also be distinguished with &lt;code&gt;--cache&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://git-scm.com/docs/git-log"&gt;git log&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git log&lt;/code&gt;&lt;br&gt;
Show all logs in detail&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git log -n 10&lt;/code&gt;&lt;br&gt;
Show latest 10 logs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git log --oneline&lt;/code&gt;&lt;br&gt;
Show all logs briefly&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git log --oneline master ^BRANCH | wc -l&lt;/code&gt;&lt;br&gt;
Compute how much commit differences between BRANCH and master&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://git-scm.com/docs/git-stash"&gt;git stash&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git stash&lt;/code&gt;&lt;br&gt;
Stash modified / deleted files, and the added files marked as to-be-committed&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git stash -u&lt;/code&gt;&lt;br&gt;
Stash modified / deleted / added files, which means the added files is included without using &lt;code&gt;git add&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git stash pop&lt;/code&gt;&lt;br&gt;
Pop the stashed files&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://git-scm.com/docs/git-revert"&gt;git revert&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git revert SHA-1&lt;/code&gt;&lt;br&gt;
Cancel a commit by forming a new commit &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git revert SHA-1 -m 1&lt;/code&gt;&lt;br&gt;
If the to-be-reverted commit is a merged one, need to designate which parent commit to be reverted to&lt;br&gt;
For example, if the merged commit is merging from BRANCH_2 to BRANCH_1, and you want to revert to BRANCH_1, then m should be 1 (it's the most cases)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://git-scm.com/docs/git-reset"&gt;git reset&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git reset&lt;/code&gt;&lt;br&gt;
Cancel marking for to-be-committed files (equivalent to withdrawing &lt;code&gt;git add&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git reset --hard&lt;/code&gt;&lt;br&gt;
Cancel updating for modified / deleted files and added files marked as to-be-committed&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git reset SHA-1&lt;/code&gt;&lt;br&gt;
Cancel all the commits after SHA-1, but retain updates of the committed files&lt;br&gt;
If want to just cancel last commit, SHA-1 can be set as &lt;code&gt;HEAD^&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git reset --hard SHA-1&lt;/code&gt;&lt;br&gt;
Cancel all the commits after SHA-1, and don't retain updates of the committed files&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://git-scm.com/docs/git-rebase"&gt;git rebase&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git rebase BRANCH&lt;/code&gt;&lt;br&gt;
Make current branch rebase BRANCH&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git rebase -i SHA-1&lt;/code&gt;&lt;br&gt;
Update commits after SHA-1, can &lt;code&gt;pick/p&lt;/code&gt;, &lt;code&gt;edit/e&lt;/code&gt;, &lt;code&gt;drop/d&lt;/code&gt;, &lt;code&gt;squash/s&lt;/code&gt; corresponding commits&lt;br&gt;
If the first commit used with &lt;code&gt;p&lt;/code&gt;, and the following commit used with &lt;code&gt;s&lt;/code&gt;, then multiple commits will join into a single commit&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://git-scm.com/docs/git-merge"&gt;git merge&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git merge BRANCH&lt;/code&gt;&lt;br&gt;
Merge BRANCH into current branch, try not to form merged commit&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git merge --no-ff BRANCH&lt;/code&gt;&lt;br&gt;
Merge BRANCH into current branch, and make sure to form merged commit&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git merge --squash BRANCH&lt;/code&gt;&lt;br&gt;
Make the differences between BRANCH and the current branch as to-be-committed contents, need to run &lt;code&gt;git commit&lt;/code&gt; to complete merging with only one commit&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://git-scm.com/docs/git-update-index"&gt;git update-index&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git update-index --assume-unchanged /PATH/TO/FILE&lt;/code&gt;&lt;br&gt;
When a file is modified temporary, but don't want to be committed, and not suitable to be added to &lt;code&gt;.gitignore&lt;/code&gt;, can use this command to make &lt;code&gt;git&lt;/code&gt; don't recognize it as modified&lt;br&gt;
Cannot use this command if the file is newly added, but can add the file path to &lt;code&gt;.git/info/exclude&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;git update-index --no-assume-unchanged /PATH/TO/FILE&lt;/code&gt;&lt;br&gt;
Recover modified recognition for the designated file&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>shell</category>
    </item>
    <item>
      <title>Performance Test Plan for Different Environment</title>
      <dc:creator>Chanvin Xiao</dc:creator>
      <pubDate>Sun, 20 Jun 2021 08:52:42 +0000</pubDate>
      <link>https://forem.com/chanvin/performance-test-plan-for-different-environment-3g9j</link>
      <guid>https://forem.com/chanvin/performance-test-plan-for-different-environment-3g9j</guid>
      <description>&lt;p&gt;The settings of JMeter performance test for different environment are basically consistent, just with differences of address, account, an so on, Here I utilize Shell Script and JMeter variable to handle uniformly &lt;/p&gt;

&lt;h2&gt;
  
  
  Install JMeter
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;wget &lt;a href="https://mirror.cogentco.com/pub/apache//jmeter/binaries/apache-jmeter-5.4.1.tgz"&gt;https://mirror.cogentco.com/pub/apache//jmeter/binaries/apache-jmeter-5.4.1.tgz&lt;/a&gt;&lt;br&gt;&lt;br&gt;
tar -zxvf apache-jmeter-5.4.1.tgz -C /opt&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Add bin directory to PATH&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export PATH="$PATH:/opt/apache-jmeter-5.4.1/bin"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;This way we can execute &lt;code&gt;jmeter&lt;/code&gt; command from anywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Config Test Plan
&lt;/h2&gt;

&lt;p&gt;Run JMeter GUI&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;jmeter&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Normal Setting
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Right click &lt;code&gt;Test Plan&lt;/code&gt;, hover &lt;code&gt;Add&lt;/code&gt;, then hove &lt;code&gt;Threads(User)&lt;/code&gt;, click &lt;code&gt;Thread Group&lt;/code&gt;, set appropriate value for &lt;code&gt;Number of Threads (users)&lt;/code&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Jx8p_pcg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/performance-test-plan-for-different-environment/threads.png%3F40" alt=""&gt;
&lt;/li&gt;
&lt;li&gt;Right click the newly added &lt;code&gt;Thread Group&lt;/code&gt; under &lt;code&gt;Test Plan&lt;/code&gt; , hover &lt;code&gt;Add&lt;/code&gt;, then hover &lt;code&gt;Config Element&lt;/code&gt;, click &lt;code&gt;HTTP Request Default&lt;/code&gt;, set &lt;code&gt;Server Name or IP&lt;/code&gt; as &lt;code&gt;${__P(HOST)}&lt;/code&gt;, &lt;code&gt;Port Number&lt;/code&gt; as &lt;code&gt;${__P(PORT)}&lt;/code&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ysDSQN4O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/performance-test-plan-for-different-environment/defaults.png" alt=""&gt;
&lt;/li&gt;
&lt;li&gt;Right click &lt;code&gt;Thread Group&lt;/code&gt;, hover &lt;code&gt;Add&lt;/code&gt;, then hover &lt;code&gt;Sample&lt;/code&gt;, click &lt;code&gt;HTTP Request&lt;/code&gt;, set &lt;code&gt;Name&lt;/code&gt; as &lt;code&gt;Frontend&lt;/code&gt;, &lt;code&gt;Path&lt;/code&gt; as &lt;code&gt;/&lt;/code&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iecvpZRM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/performance-test-plan-for-different-environment/frontend.png%3F40" alt=""&gt;
&lt;/li&gt;
&lt;li&gt;Right click &lt;code&gt;Thread Group&lt;/code&gt;, hover &lt;code&gt;Add&lt;/code&gt;, then hover &lt;code&gt;Sample&lt;/code&gt;, click &lt;code&gt;HTTP Request&lt;/code&gt;, set &lt;code&gt;Name&lt;/code&gt; as &lt;code&gt;Backend&lt;/code&gt;, &lt;code&gt;Path&lt;/code&gt; as &lt;code&gt;/api/currentUser&lt;/code&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--erMSgGBe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/performance-test-plan-for-different-environment/backend.png%3F40" alt=""&gt;
&lt;/li&gt;
&lt;li&gt;Right click &lt;code&gt;Thread Group&lt;/code&gt;, hover &lt;code&gt;Add&lt;/code&gt;, then hover &lt;code&gt;Assertion&lt;/code&gt;, click &lt;code&gt;Response Assertion&lt;/code&gt;, set &lt;code&gt;Field to Test&lt;/code&gt; as &lt;code&gt;Response Code&lt;/code&gt;, &lt;code&gt;Patten Matching Rules&lt;/code&gt; as &lt;code&gt;Equals&lt;/code&gt;, click &lt;code&gt;Add&lt;/code&gt; in the bottom, input &lt;code&gt;200&lt;/code&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iKiBy1qi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/performance-test-plan-for-different-environment/assertion.png%3F40" alt=""&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://jmeter.apache.org/usermanual/functions.html#__P"&gt;&lt;code&gt;__P&lt;/code&gt;&lt;/a&gt; is function for JMeter to get the command line parameter&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Handle Authorization
&lt;/h3&gt;

&lt;p&gt;The above backend api can only be access after login, the authorized method is token in the cookies or Authorization Header, so we need to get this token firstly, and append to every request&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Right click &lt;code&gt;Test Plan&lt;/code&gt;, hover &lt;code&gt;Add&lt;/code&gt;, then hover &lt;code&gt;Threads(User)&lt;/code&gt;, click &lt;code&gt;setUp Thread Group&lt;/code&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--o232Vk12--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/performance-test-plan-for-different-environment/setup.png%3F40" alt=""&gt;
&lt;/li&gt;
&lt;li&gt;Right click &lt;code&gt;setUp Thread Group&lt;/code&gt;, hover &lt;code&gt;Add&lt;/code&gt;, then hover &lt;code&gt;Sample&lt;/code&gt;, click &lt;code&gt;HTTP Request&lt;/code&gt;, set &lt;code&gt;Name&lt;/code&gt; as &lt;code&gt;Login&lt;/code&gt;, &lt;code&gt;Server Name or IP&lt;/code&gt; as &lt;code&gt;${__P(HOST)}&lt;/code&gt;, &lt;code&gt;Port Number&lt;/code&gt; as &lt;code&gt;${__P(PORT)}&lt;/code&gt;, Method of &lt;code&gt;HTTP Request&lt;/code&gt; as &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;Path&lt;/code&gt; as &lt;code&gt;/api/login/account&lt;/code&gt;, click &lt;code&gt;Add&lt;/code&gt; in the bottom, add &lt;code&gt;Name&lt;/code&gt; as &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;Value&lt;/code&gt; as &lt;code&gt;${__P(USERNAME)}&lt;/code&gt; and &lt;code&gt;${__P(PASSWORD)}&lt;/code&gt; respectively
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KxJK5QQJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/performance-test-plan-for-different-environment/login.png%3F40" alt=""&gt;
&lt;/li&gt;
&lt;li&gt;Right click &lt;code&gt;setUp Thread Group&lt;/code&gt;, hover &lt;code&gt;Add&lt;/code&gt;, then hover &lt;code&gt;Post Processors&lt;/code&gt;, click &lt;code&gt;Regular Expression Extractor&lt;/code&gt;, set &lt;code&gt;Name of created variable&lt;/code&gt; as &lt;code&gt;token&lt;/code&gt;, &lt;code&gt;Regular Expression&lt;/code&gt; as &lt;code&gt;"token":"(.+?)"&lt;/code&gt;, Template as &lt;code&gt;$1$&lt;/code&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ax2Xwt3d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/performance-test-plan-for-different-environment/regular.png%3F40" alt=""&gt;
&lt;/li&gt;
&lt;li&gt;Right click &lt;code&gt;setUp Thread Group&lt;/code&gt;, hover &lt;code&gt;Add&lt;/code&gt;, then hover &lt;code&gt;Sample&lt;/code&gt;, click &lt;code&gt;BeanShellSampler&lt;/code&gt;, set &lt;code&gt;Name&lt;/code&gt; as &lt;code&gt;Token&lt;/code&gt;, &lt;code&gt;Script&lt;/code&gt; as &lt;code&gt;${__setProperty(token, ${token})}&lt;/code&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yCEZ5LoX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/performance-test-plan-for-different-environment/token.png%3F40" alt=""&gt;
&lt;/li&gt;
&lt;li&gt;Right click &lt;code&gt;Thread Group&lt;/code&gt;, hover &lt;code&gt;Add&lt;/code&gt;, then hover &lt;code&gt;Config Element&lt;/code&gt;, click &lt;code&gt;HTTP Cookie Manager&lt;/code&gt;, click &lt;code&gt;Add&lt;/code&gt; in the bottom, set &lt;code&gt;Name&lt;/code&gt; as &lt;code&gt;token&lt;/code&gt;, &lt;code&gt;Value&lt;/code&gt; as &lt;code&gt;${__property(token)}&lt;/code&gt;, &lt;code&gt;Domain&lt;/code&gt; as &lt;code&gt;${__P(HOST)}:${__P(PORT)}&lt;/code&gt;, &lt;code&gt;Path&lt;/code&gt; as &lt;code&gt;/&lt;/code&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bMYL5bhC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/performance-test-plan-for-different-environment/cookies.png" alt=""&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;"token":"(.+?)"&lt;/code&gt; capture value of &lt;code&gt;token&lt;/code&gt; in the return result as &lt;code&gt;$1$&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jmeter.apache.org/usermanual/functions.html#__setProperty"&gt;&lt;code&gt;__setProperty&lt;/code&gt;&lt;/a&gt; is function for JMeter to set global variable&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jmeter.apache.org/usermanual/functions.html#__property"&gt;&lt;code&gt;__property&lt;/code&gt;&lt;/a&gt; is function for JMeter to get global variable&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run Test Plan
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Run Directly
&lt;/h3&gt;

&lt;p&gt;The above plan config set information of address and account as variable, which can be transmitted in the command line, such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;mkdir report&lt;br&gt;&lt;br&gt;
HEAP="-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m"&lt;br&gt;&lt;br&gt;
jmeter -n -t test.jmx \&lt;br&gt;&lt;br&gt;
  -J HOST=127.0.0.1 \&lt;br&gt;&lt;br&gt;
  -J PORT=80 \&lt;br&gt;&lt;br&gt;
  -J USERNAME=USERNAME \&lt;br&gt;&lt;br&gt;
  -J PASSWORD=PASSWORD \&lt;br&gt;&lt;br&gt;
  -l log/local-20210620095700.txt \&lt;br&gt;&lt;br&gt;
  -e -o report/local-20210620095700  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;report&lt;/code&gt; directory must exists, so that the report can be written, so &lt;code&gt;mkdir&lt;/code&gt; need to be used to create it for the first time&lt;/li&gt;
&lt;li&gt;The above command line make use of &lt;a href="https://jmeter.apache.org/usermanual/get-started.html#options"&gt;jmeter command line options&lt;/a&gt; &lt;code&gt;-n&lt;/code&gt;, &lt;code&gt;-t&lt;/code&gt;, &lt;code&gt;-J&lt;/code&gt;, &lt;code&gt;-l&lt;/code&gt;, &lt;code&gt;-e&lt;/code&gt; and &lt;code&gt;-o&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The html formatted test report in local-20210620095700, is composed by table data and visualized chart, as the following images shown:
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W3aj81tU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/performance-test-plan-for-different-environment/dash.png" alt=""&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lMp0Cx3G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/performance-test-plan-for-different-environment/time.png" alt=""&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Handle by Shell
&lt;/h3&gt;

&lt;p&gt;The above commands are verbose, and we need to set every parameter for different environment, so I utilize Shell Script to simplify&lt;/p&gt;

&lt;h4&gt;
  
  
  Config Variable
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export LOCAL_HOST=127.0.0.1
export LOCAL_PORT=80
export LOCAL_USERNAME=USERNAME
export LOCAL_PASSWORD=PASSWORD
export TESTING_HOST=192.168.1.10
export TESTING_PORT=80
export TESTING_USERNAME=USERNAME
export TESTING_PASSWORD=PASSWORD
export PRODUCT_HOST=192.168.1.100
export PRODUCT_PORT=80
export PRODUCT_USERNAME=USERNAME
export PRODUCT_PASSWORD=PASSWORD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Invoke Variable
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/sh

time=$(date "+%Y%m%d%H%M%S")
server=${1:-"local"}
prefix=$(echo $server | tr 'a-z' 'A-Z')_
HOST=${prefix}HOST
PORT=${prefix}PORT
USERNAME=${prefix}USERNAME
PASSWORD=${prefix}PASSWORD

if ! [ -d "report" ]
then
mkdir report
fi

HEAP="-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m"
jmeter -n -t test.jmx \
  -J HOST=${!HOST} \
  -J PORT=${!PORT} \
  -J USERNAME=${!USERNAME} \
  -J PASSWORD=${!PASSWORD} \
  -l log/$server-$time.txt \
  -e -o report/$server-$time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;$(echo $VAR | tr 'a-z' 'A-Z')&lt;/code&gt; indicates uppercase for all, same as &lt;code&gt;${VAR^^}&lt;/code&gt; for &lt;code&gt;Bash&lt;/code&gt; which above version 4.0 &lt;/li&gt;
&lt;li&gt;If &lt;code&gt;report&lt;/code&gt; directory exists, we don't need to recreate it again, so we determine with &lt;code&gt;-d&lt;/code&gt; firstly&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;${!HOST}&lt;/code&gt; means the value of &lt;code&gt;$HOST&lt;/code&gt; is the name the other variable, and the value of this other variable will be resolved&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Run Script
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Local Environment
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;./test.sh  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Testing Environment
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;./test.sh testing  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Product Environment
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;./test.sh product  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;if the environment parameter is omitted, &lt;code&gt;${1:-"local"}&lt;/code&gt; will set the default environment as &lt;code&gt;local&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summarize
&lt;/h2&gt;

&lt;p&gt;The above plan and script can be downloaded via &lt;a href="https://github.com/vinzid/perf-test-plan"&gt;this GitHub Repository&lt;/a&gt;, which fulfills the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Config test plan dynamically for JMeter through the reception of parameter inputted by command line&lt;/li&gt;
&lt;li&gt;Utilize setup thread group to handle login, and set token as global variable&lt;/li&gt;
&lt;li&gt;Simplify command and handle uniformly for different environment with Shell Script&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>performance</category>
      <category>jmeter</category>
      <category>shell</category>
    </item>
    <item>
      <title>Cache Control for React App with Nginx</title>
      <dc:creator>Chanvin Xiao</dc:creator>
      <pubDate>Sat, 29 May 2021 23:34:50 +0000</pubDate>
      <link>https://forem.com/chanvin/cache-control-for-react-app-with-nginx-2oef</link>
      <guid>https://forem.com/chanvin/cache-control-for-react-app-with-nginx-2oef</guid>
      <description>&lt;p&gt;The cache issue which typical React App confronts, can be solved by Nginx configuration&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Deployment
&lt;/h2&gt;

&lt;p&gt;After the app is built, we can just use Nginx to point to the static files&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

server {
  listen 80;
  root /PATH/TO/APP/build;
  try_files $uri $uri/ /index.html;
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Cache Issue
&lt;/h2&gt;

&lt;p&gt;When access page for the first time, all the page and resources are from server, as the following image shown:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fchanvinxiao.com%2Fcn%2Fblog%2Fcache-control-for-react-app-with-nginx%2Ffresh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fchanvinxiao.com%2Fcn%2Fblog%2Fcache-control-for-react-app-with-nginx%2Ffresh.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Close the browser, then reopen, Input address, press Enter, the browser will get the cache from local, as the following image shown:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fchanvinxiao.com%2Fcn%2Fblog%2Fcache-control-for-react-app-with-nginx%2Fcache.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fchanvinxiao.com%2Fcn%2Fblog%2Fcache-control-for-react-app-with-nginx%2Fcache.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even if the page was updated between the two request, the browser would not get the update from the server, since &lt;code&gt;disk cache&lt;/code&gt; does not communicate with server&lt;/p&gt;
&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;If the resource file is updated, the file name will changed, so the cache of resource won't be a problem, we just need to disable the cache of page&lt;/p&gt;

&lt;p&gt;Just replace&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

  try_files $uri $uri/ /index.html;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

  location / {
    if ( $uri = '/index.html' ) {
      add_header Cache-Control no-store always;
    }
    try_files $uri $uri/ /index.html;
  }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Since all the pages is eventually pointed to the entrance file, so all the actual &lt;a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#var_uri" rel="noopener noreferrer"&gt;&lt;code&gt;$uri&lt;/code&gt;&lt;/a&gt; are &lt;code&gt;/index.html&lt;/code&gt; for pages&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;no-store&lt;/code&gt; is the strictest value for &lt;a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control" rel="noopener noreferrer"&gt;&lt;code&gt;Cache-Control&lt;/code&gt;&lt;/a&gt; to disable cache, to make sure the browser won't use any cache&lt;/li&gt;
&lt;li&gt;Since &lt;a href="http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header" rel="noopener noreferrer"&gt;&lt;code&gt;add_header&lt;/code&gt;&lt;/a&gt; with &lt;code&gt;if&lt;/code&gt; can not be placed directly inside &lt;code&gt;server&lt;/code&gt;, so we need to add the location layer &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;This way, when we request page the second time, the page won't cache, but the resources will be cached if no change, as the following image shown:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fchanvinxiao.com%2Fcn%2Fblog%2Fcache-control-for-react-app-with-nginx%2Findex.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fchanvinxiao.com%2Fcn%2Fblog%2Fcache-control-for-react-app-with-nginx%2Findex.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can access the following address, try to operate, and inspect the corresponding network request:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="http://react.chanvinxiao.com" rel="noopener noreferrer"&gt;http://react.chanvinxiao.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Weird cache problem will arise when input address and press Enter in browser&lt;/li&gt;
&lt;li&gt;We can determine if the request is page through &lt;code&gt;$uri&lt;/code&gt; of Nginx&lt;/li&gt;
&lt;li&gt;The Cache Control header can be set via &lt;code&gt;add_header&lt;/code&gt; of Nginx&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>nginx</category>
      <category>cache</category>
    </item>
    <item>
      <title>Utilize Nginx to fulfill bilingualism for Hexo</title>
      <dc:creator>Chanvin Xiao</dc:creator>
      <pubDate>Sun, 21 Jun 2020 12:19:54 +0000</pubDate>
      <link>https://forem.com/chanvin/utilize-nginx-to-fulfill-bilingualism-for-hexo-1633</link>
      <guid>https://forem.com/chanvin/utilize-nginx-to-fulfill-bilingualism-for-hexo-1633</guid>
      <description>&lt;p&gt;The bilingualism config process for &lt;a href="https://hexo.io/"&gt;Hexo&lt;/a&gt; is recorded In this article. Different language version is in the same application with common template, any webpage can be switched to another language's corresponding webpage, and for the access url whose language is not specific, automatic redirection will be proceed according to browser language &lt;/p&gt;

&lt;h2&gt;
  
  
  Fulfillment Rule
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Address Difference
&lt;/h3&gt;

&lt;p&gt;Chinese Home:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://chanvinxiao.com/cn/blog/"&gt;https://chanvinxiao.com/cn/blog/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;English Home:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://chanvinxiao.com/en/blog/"&gt;https://chanvinxiao.com/en/blog/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Switch between Languages
&lt;/h3&gt;

&lt;p&gt;The following Chinese page as example&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://chanvinxiao.com/cn/blog/archives/2020/04/"&gt;https://chanvinxiao.com/cn/blog/archives/2020/04/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Click &lt;code&gt;English&lt;/code&gt; in the top right corner, the following webpage will be shown&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://chanvinxiao.com/en/blog/archives/2020/04/"&gt;https://chanvinxiao.com/en/blog/archives/2020/04/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Click &lt;code&gt;中文&lt;/code&gt; in the top right corner in this webpage, the previous webpage will been shown&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic Redirection
&lt;/h3&gt;

&lt;p&gt;The following address as example&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://chanvinxiao.com/blog/vuejs-tic-tac-toe/"&gt;https://chanvinxiao.com/blog/vuejs-tic-tac-toe/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the browser prefer language is set to English, it will redirect to corresponding English version &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://chanvinxiao.com/en/blog/vuejs-tic-tac-toe/"&gt;https://chanvinxiao.com/en/blog/vuejs-tic-tac-toe/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the browser prefer language is set to Chinese, it will redirect to corresponding Chinese version &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://chanvinxiao.com/cn/blog/vuejs-tic-tac-toe/"&gt;https://chanvinxiao.com/cn/blog/vuejs-tic-tac-toe/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Hexo Setting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Add English Setting
&lt;/h3&gt;

&lt;p&gt;Add &lt;code&gt;_config-en.yml&lt;/code&gt; in the root directory&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Site
title: TITLE
subtitle: SUBTITLE
description: DESCRIPTION
keywords: KEYWORDS
language: en

# URL
url: https://chanvinxiao.com/en/blog
root: /en/blog/

# Directory
source_dir: source-en
public_dir: public-en
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;in the &lt;code&gt;#Site&lt;/code&gt; associated setting, I change the original Chinese content to English, and the key point is to change &lt;code&gt;language&lt;/code&gt; to &lt;code&gt;en&lt;/code&gt;, thus the template will use english version&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;URL&lt;/code&gt; and &lt;code&gt;root&lt;/code&gt; need to be set as individual address and directory to distinguish between chinese counterpart&lt;/li&gt;
&lt;li&gt;Divide &lt;code&gt;source&lt;/code&gt; and &lt;code&gt;public&lt;/code&gt; directory from chinese, to ensure the Chinese or English version only show Chinese or English article respectively &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Add Script
&lt;/h3&gt;

&lt;p&gt;Add the following script in &lt;code&gt;package.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  "scripts": {
    ...
    "build:en": "hexo generate --config _config.yml,_config-en.yml",
    "clean:en": "hexo clean --config _config.yml,_config-en.yml",
    "server:en": "hexo server --config _config.yml,_config-en.yml"
  },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Add build, clear, and server script for English version, so the Chinese and English version is separated without interfering with each other&lt;/li&gt;
&lt;li&gt;Utilize &lt;a href="https://hexo.io/docs/configuration#Using-an-Alternate-Config"&gt;Custom Config&lt;/a&gt;, combine config &lt;code&gt;_config.yml&lt;/code&gt; and &lt;code&gt;_config-en.yml&lt;/code&gt; in the corresponding scripts&lt;/li&gt;
&lt;li&gt;The combining config file &lt;code&gt;_multiconfig.yml&lt;/code&gt; will be generated, which should be added to .gitignore&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Nginx Config
&lt;/h2&gt;

&lt;p&gt;Add the following config in corresponding &lt;code&gt;server&lt;/code&gt; session in Nginx&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  if ( $http_accept_language ~* ^en ) {
    rewrite ^(/blog.*) /en$1 redirect;
  }

  rewrite ^(/blog.*) /cn$1 redirect;

  location /cn/blog {
    alias /PATH/TO/BLOG/public;
    error_page 404 $scheme://$host/cn/blog;
  }

  location /en/blog {
    alias /PATH/TO/BLOG/public-en;
    error_page 404 $scheme://$host/en/blog;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;$http_accept_language&lt;/code&gt; is &lt;a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#variables"&gt;embedded variable&lt;/a&gt; set by Nginx's &lt;code&gt;http&lt;/code&gt; module for request header &lt;code&gt;Accept-Language&lt;/code&gt;, If prefer language of browser is English, the value will start with &lt;code&gt;en&lt;/code&gt;, such as &lt;code&gt;en-US,en;q=0.9&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rewrite ^(/blog.*) /en$1 redirect;&lt;/code&gt; will add en  for the address start with /blog, the flag of &lt;a href="http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#rewrite"&gt;&lt;code&gt;rewrite&lt;/code&gt;&lt;/a&gt; is set to &lt;code&gt;redirect&lt;/code&gt;, which means 302 redirect, so does the following default cn&lt;/li&gt;
&lt;li&gt;The above setting make redirect decision for address start with &lt;code&gt;/blog&lt;/code&gt; (aka address without language), If the prefer language of browser is English, English site start with &lt;code&gt;/en/blog&lt;/code&gt; will be redirected, or else Chinese version start with &lt;code&gt;/cn/blog&lt;/code&gt; will be redirected&lt;/li&gt;
&lt;li&gt;Because /cn/blog is matched with index.html in the public directory, not cn/blog/index.html, so here &lt;a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#alias"&gt;&lt;code&gt;alias&lt;/code&gt;&lt;/a&gt; is used, not &lt;a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#root"&gt;&lt;code&gt;root&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#error_page"&gt;&lt;code&gt;error_page&lt;/code&gt;&lt;/a&gt; is set for 404 handling, &lt;code&gt;$scheme&lt;/code&gt; is &lt;code&gt;http&lt;/code&gt; or &lt;code&gt;https&lt;/code&gt;, which means webpage redirect, and will redirect to Chinese or English homepage&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Webpage Switch
&lt;/h2&gt;

&lt;p&gt;Use template landscape as example, add the following content before &lt;code&gt;})(jQuery);&lt;/code&gt; in &lt;code&gt;themes/landscape/source/js/script.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  let language = {};
  language.now = location.pathname.match(/^\/en/) ? 'en' : 'cn';
  if('en' === language.now){
    language.label = '中文';
    language.href = location.pathname.replace(/^\/en/, '/cn');
  }else{
    language.label = 'English';
    language.href = location.pathname.replace(/^\/cn/, '/en');
  }
  $('#sub-nav').prepend(`&amp;lt;a class="main-nav-link" href="${language.href}"&amp;gt;${language.label}&amp;lt;/a&amp;gt;`)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Determine the language of current webpage according to the existence of &lt;code&gt;/en&lt;/code&gt; in the beginning of webpage path&lt;/li&gt;
&lt;li&gt;Add link menu to Chinese webpage in English webpage, and add English link in Chinese version&lt;/li&gt;
&lt;li&gt;Directly change &lt;code&gt;cn&lt;/code&gt; to &lt;code&gt;en&lt;/code&gt; or &lt;code&gt;en&lt;/code&gt; to &lt;code&gt;cn&lt;/code&gt; in the address to get the corresponding webpage, if there is not corresponding webpage, the associated homepage will be redirected according to the above Nginx config&lt;/li&gt;
&lt;li&gt;Utilize &lt;code&gt;prepend&lt;/code&gt; of &lt;code&gt;jQuery&lt;/code&gt; to add link to the sub menu, with the common class &lt;code&gt;main-nav-link&lt;/code&gt; style&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summarize
&lt;/h2&gt;

&lt;p&gt;In the fulfillment of blog bilingualism, the following technology is primarily used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom config of hexo and &lt;code&gt;scripts&lt;/code&gt; in package.json&lt;/li&gt;
&lt;li&gt;Embedded variable for request header in &lt;code&gt;http&lt;/code&gt; module of Nginx&lt;/li&gt;
&lt;li&gt;Nginx's directive  &lt;code&gt;rewrite&lt;/code&gt;, &lt;code&gt;alias&lt;/code&gt; and &lt;code&gt;error_page&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pathname&lt;/code&gt; of location and &lt;code&gt;prepend&lt;/code&gt; of jQuery&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hexo</category>
      <category>nginx</category>
      <category>jquery</category>
    </item>
    <item>
      <title>Prefetch and Preload with Handling in Webpack</title>
      <dc:creator>Chanvin Xiao</dc:creator>
      <pubDate>Sun, 07 Jun 2020 11:34:38 +0000</pubDate>
      <link>https://forem.com/chanvin/prefetch-and-preload-with-handling-in-webpack-593h</link>
      <guid>https://forem.com/chanvin/prefetch-and-preload-with-handling-in-webpack-593h</guid>
      <description>&lt;p&gt;&lt;code&gt;prefetch&lt;/code&gt; and &lt;code&gt;preload&lt;/code&gt; usage is good way to optimize website performance and user experience, this article introduce method to prefetch and preload, and fullfil with webpack&lt;/p&gt;

&lt;h2&gt;
  
  
  Link Type
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;rel&lt;/code&gt; property of &lt;a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/link"&gt;&lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt;&lt;/a&gt; tag is used to defined link type, and combined with &lt;code&gt;href&lt;/code&gt; can prefetch and preload corresponding resource. &lt;code&gt;prefetch&lt;/code&gt; is one of the link types&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="prefetch" herf="URL"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;preload&lt;/code&gt; is the other type, also use href to defined resource address, but besides handling pre fetch, it will resolve the resource, so the property &lt;code&gt;as&lt;/code&gt; need to be added, to indicate type of resource&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="preload" href="URL" as="MIME_TYPE"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Prefetch Resource
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;prefetch&lt;/code&gt; indicates that in the following navigation (ie: next webpage), user may need to use corresponding resource, and give a hint to browser that corresponding resource need to be gotten when idle&lt;/p&gt;

&lt;p&gt;First create a new diretory &lt;code&gt;prefetch-preload-demo&lt;/code&gt; (all the code in this article will create here), install the associated dependencies, and create new directory &lt;code&gt;static&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;mkdir prefetch-preload-demo&lt;br&gt;&lt;br&gt;
cd prefetch-preload-demo&lt;br&gt;&lt;br&gt;
npm init -y&lt;br&gt;&lt;br&gt;
npm i -D http-server&lt;br&gt;&lt;br&gt;
mkdir static&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In static directory, create &lt;code&gt;prefetch.html&lt;/code&gt;， &lt;code&gt;main.js&lt;/code&gt; and &lt;code&gt;script.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;prefetch.html&lt;/code&gt; defined a link with &lt;code&gt;prefetch&lt;/code&gt; as &lt;code&gt;rel&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;title&amp;gt;Prefetch&amp;lt;/title&amp;gt;
&amp;lt;meta charset="utf-8"&amp;gt;
&amp;lt;meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"&amp;gt;
&amp;lt;link rel="prefetch" href="script.js"&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;script src="main.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;main.js&lt;/code&gt; create a button, and bind click event&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let button = document.createElement('button');
button.innerHTML = 'Add Script';
button.addEventListener('click', e =&amp;gt; {
  let script = document.createElement("script");
  script.src = "script.js";
  document.head.appendChild(script);
});
document.body.appendChild(button);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;script.js&lt;/code&gt; just simply print&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.log('script run');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the sever (or add &lt;code&gt;server&lt;/code&gt; script in &lt;code&gt;package.json&lt;/code&gt;)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;npx http-server&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Access &lt;a href="http://localhost:8080"&gt;http://localhost:8080&lt;/a&gt; and navigate to &lt;code&gt;static&lt;/code&gt;, click prefetch.html, or directly visit online &lt;a href="https://chanvinxiao.com/demo/html/prefetch.html"&gt;webpage&lt;/a&gt;, in the initial status, check the network tab of devtool, as the following image (don't check &lt;code&gt;Disable Cache&lt;/code&gt;, click the gear on the right, check &lt;code&gt;Use large request rows&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FGdIA-sM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/prefetch-and-preload-with-webpack/prefetch-init.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FGdIA-sM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/prefetch-and-preload-with-webpack/prefetch-init.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;script.js&lt;/code&gt; is fetched, the two number in the size column, 275 B shows the download size, 0 B shows the resolved size (which means that it's not been resolved yet)&lt;/li&gt;
&lt;li&gt;Console tab is empty, which means script has not run yet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click &lt;code&gt;Add Script&lt;/code&gt; on the page, &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag with address &lt;code&gt;script.js&lt;/code&gt; will be added on the page, and the following content will been added on the network tab &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QZ2ub8V5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/prefetch-and-preload-with-webpack/prefetch-add.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QZ2ub8V5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/prefetch-and-preload-with-webpack/prefetch-add.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download size become &lt;code&gt;(prefetch cache)&lt;/code&gt;, which means that the resource is directly gotten from prefetch cache, and the resolved size below is no longer 0&lt;/li&gt;
&lt;li&gt;The debug content is printed on the console, which means that the script is resolved and run just now&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Preload Resource
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;preload&lt;/code&gt; indicates that in the current navigation (mostly the current webpage), user very likely need to use corresponding resource, and give a hint to browser that corresponding resource need to be gotten preferentially&lt;/p&gt;

&lt;p&gt;Change prefetch in link tag of prefetch.html to &lt;code&gt;preload&lt;/code&gt;, and set resource type &lt;code&gt;as&lt;/code&gt; to &lt;code&gt;script&lt;/code&gt;, which form preload.html&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="preload" href="script.js" as="script"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Access the local server corresponding &lt;code&gt;prefetch.html&lt;/code&gt;, or directly access online &lt;a href="https://chanvinxiao.com/demo/html/preload.html"&gt;webpage&lt;/a&gt;, in the initial status, check the network tag of devtool&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bjUTOLVt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/prefetch-and-preload-with-webpack/preload.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bjUTOLVt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chanvinxiao.com/cn/blog/prefetch-and-preload-with-webpack/preload.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;script.js&lt;/code&gt; is downloaded preferentially, the resolved size of size column is no longer 0, namely &lt;code&gt;preload&lt;/code&gt; no only download the script, but also resolve it&lt;/li&gt;
&lt;li&gt;The console tab is still empty, namely although the script is resolved, it has not run yet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click &lt;code&gt;Add Script&lt;/code&gt;, there is not additional record in the network tab, but the console output the print content of script&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Because script is resolved completely, there is even no need to fetch from cache, just directly run&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;Add Script&lt;/code&gt; is not clicked in 3 second, the console will output warning, because the resource which should be loaded preferentially is not used in time
&amp;gt;The resource &lt;a href="https://chanvinxiao.com/demo/html/script.js"&gt;https://chanvinxiao.com/demo/html/script.js&lt;/a&gt; was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate &lt;code&gt;as&lt;/code&gt; value and it is preloaded intentionally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  webpack associated handling
&lt;/h2&gt;

&lt;p&gt;Run the following command to install corresponding dependencies, and create new directory src&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;npm i -D webpack webpack-cli html-webpack-plugin preload-webpack-plugin\@3.0.0-beta.4&lt;br&gt;&lt;br&gt;
mkdir src&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Current version of PreloadWebpackPlugin is 2.x, which is not compatible with current version of webpack which is 4.x, so need to specify the version as the newest 3.x beta&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;copy &lt;code&gt;main.js&lt;/code&gt; and &lt;code&gt;script.js&lt;/code&gt; to src, and modify the click event handler in &lt;code&gt;main.js&lt;/code&gt; to the following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;button.addEventListener('click', e =&amp;gt; {
  import(/* webpackChunkName: "script" */ './script.js');
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://webpack.js.org/api/module-methods/#import-1"&gt;import()&lt;/a&gt; will load script dynamically, webpack will generate code which create &lt;code&gt;script&lt;/code&gt; tag similar to above&lt;/li&gt;
&lt;li&gt;The comment in import is magical comment with special meaning, and if webpackChunkName is not set, the imported script will name with number sequence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add &lt;code&gt;webpack.config.js&lt;/code&gt; as following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const HtmlWebpackPlugin = require('html-webpack-plugin');
const PreloadWebpackPlugin = require('preload-webpack-plugin');

module.exports = {
  entry: './src/main.js',
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'preload.html'
    }),
    new PreloadWebpackPlugin()
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://webpack.js.org/plugins/html-webpack-plugin/"&gt;HtmlWebpackPlugin&lt;/a&gt; will generate corresponding html file automatically, index.html as default, here we change it via option &lt;code&gt;filename&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/GoogleChrome/preload-webpack-plugin"&gt;PreloadWebpackPlugin&lt;/a&gt; is plugin of HtmlWebpackPlugin, it will add &lt;code&gt;link&lt;/code&gt; tag with link type as &lt;code&gt;preload&lt;/code&gt; for the dynamically imported resource, and the &lt;code&gt;as&lt;/code&gt; value can be determined by the suffix&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PreloadWebpackPlugin also support prefetch, with &lt;code&gt;rel&lt;/code&gt; option as &lt;code&gt;prefetch&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    new HtmlWebpackPlugin({
      filename: 'prefetch.html'
    }),
    new PreloadWebpackPlugin({
      rel: 'prefetch'
    })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if we want to generate both preload.html and prefetch.html, option &lt;code&gt;excludeHtmlNames&lt;/code&gt; need to be set in the corresponding PreloadWebpackPlugin configuration, or else both preload and prefetch link tags will be produced simultaneously&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    new HtmlWebpackPlugin({
      filename: 'preload.html'
    }),
    new HtmlWebpackPlugin({
      filename: 'prefetch.html'
    }),
    new PreloadWebpackPlugin({
      excludeHtmlNames: ['prefetch.html']
    }),
    new PreloadWebpackPlugin({
      rel: 'prefetch',
      excludeHtmlNames: ['preload.html']
    })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build files (or add &lt;code&gt;build&lt;/code&gt; script in &lt;code&gt;package.json&lt;/code&gt;)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;npx webpack&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;prefetch.html and preload.html will be generated in &lt;code&gt;dist&lt;/code&gt; directory, access local server corresponding address, you will get the same effect as the above static page&lt;/p&gt;

&lt;h2&gt;
  
  
  Summarize
&lt;/h2&gt;

&lt;p&gt;This article show the fulfillment of prefetch and preload with two methods: static page and webpack building. The complete code can be found in &lt;a href="https://github.com/vinzid/prefetch-preload-demo"&gt;GitHub&lt;/a&gt;, the main technical points are as follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ELEMENT.appendChild&lt;/code&gt; create script dynamically&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;import()&lt;/code&gt; load script dynamically and set the magical comment&lt;/li&gt;
&lt;li&gt;The configuration of &lt;code&gt;html-webpack-plugin&lt;/code&gt; and its plugin&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>html</category>
      <category>webpack</category>
      <category>performance</category>
    </item>
    <item>
      <title>Utilize Github's Webhook to Fulfill Automatic Deployment</title>
      <dc:creator>Chanvin Xiao</dc:creator>
      <pubDate>Sun, 31 May 2020 08:27:24 +0000</pubDate>
      <link>https://forem.com/chanvin/utilize-github-s-webhook-to-fulfill-automatic-deployment-4870</link>
      <guid>https://forem.com/chanvin/utilize-github-s-webhook-to-fulfill-automatic-deployment-4870</guid>
      <description>&lt;p&gt;Github's &lt;a href="https://developer.github.com/webhooks/"&gt;webhook&lt;/a&gt; functionality can fulfill automatic deployment conveniently. This article records the process of development and deployment via Node.js, when the master branch is pushed, the project will be automatically deployed, the complete code is on &lt;a href="https://github.com/vinzid/github-webhook/blob/master/app.js"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Webhook
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;On the homepage of the corresponding project of Github, click menu &lt;code&gt;Setting&lt;/code&gt; in top right corner, click menu &lt;code&gt;Webhooks&lt;/code&gt; on left side, click button &lt;code&gt;Add webhook&lt;/code&gt; of top right corner&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set &lt;code&gt;Payload URL&lt;/code&gt; as address which will receive event, suggested &lt;code&gt;Payload URL&lt;/code&gt; should be &lt;code&gt;applicaiton/json&lt;/code&gt;, &lt;code&gt;Secret&lt;/code&gt; is optional, and can be any string, choose &lt;code&gt;Just the push event.&lt;/code&gt; for &lt;code&gt;Which events would you like to trigger this webhook?&lt;/code&gt;, check &lt;code&gt;Active&lt;/code&gt;, click button &lt;code&gt;Add webhook&lt;/code&gt; below&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Develop Request Handling
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Receive Request
&lt;/h3&gt;

&lt;p&gt;Utilize &lt;code&gt;Node.js&lt;/code&gt; to setup a &lt;code&gt;http&lt;/code&gt; server, receive &lt;code&gt;POST&lt;/code&gt; request and handle the submitted data&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { createServer } = require('http');
const port = process.env.GITHUB_WEBHOOK_PORT || '3000';

const server = createServer((req, res) =&amp;gt; {
  if('POST' === req.method){
    let body = '';
    req.on('data', chunk =&amp;gt; {
      body += chunk.toString();
    });
    req.on('end', () =&amp;gt; {
    });
  }
})

server.listen(port, () =&amp;gt; {
  console.log(`Listening on ${port}`);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if the default port &lt;code&gt;3000&lt;/code&gt; need to be changed, you can firstly run the following command to add environment variable (&lt;code&gt;NUMBER&lt;/code&gt; can be any port)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;export GITHUB_WEBHOOK_PORT=NUMBER&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Parse Body
&lt;/h3&gt;

&lt;p&gt;In the &lt;code&gt;end&lt;/code&gt; event handler of &lt;code&gt;req&lt;/code&gt;, parse string &lt;code&gt;body&lt;/code&gt; to object&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    req.on('end', () =&amp;gt; {
      try{
        body = JSON.parse(decodeURIComponent(body).replace(/^payload=/, ''));
      }catch(e){
        console.log(e)
      }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;Content type&lt;/code&gt; is set to &lt;code&gt;applicaiton/json&lt;/code&gt;, just &lt;code&gt;body = JSON.parse(body)&lt;/code&gt; is sufficient, the above code add compatibility of situation when &lt;code&gt;Content type&lt;/code&gt; is set to &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pull Updates
&lt;/h3&gt;

&lt;p&gt;According to push &lt;a href="https://developer.github.com/v3/activity/events/types/#pushevent"&gt;payload&lt;/a&gt; for body, extract project and branch information, if it's &lt;code&gt;master&lt;/code&gt; branch, command to enter corresponding project and pull the branch will be executed&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      if('object' === typeof body){
        if('refs/heads/master' === body.ref){
          const { exec } = require('child_process');
          const command = `cd ../${body.repository.name} &amp;amp;&amp;amp; git pull origin master`;
          exec(command, (error, stdout, stderr) =&amp;gt; {
          });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the directory where project locates, and the directory where this application locates, are in the same parent directory, or else the entry path in the command should be adjusted&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify Secret
&lt;/h3&gt;

&lt;p&gt;The above step have fulfilled automatically pull updates, but there's security issue, because not only Github can send this kind of request, so it's better to set &lt;code&gt;Secret&lt;/code&gt; and proceed security verification&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const secret = process.env.GITHUB_WEBHOOK_SECRET || '';
...
    req.on('end', () =&amp;gt; {
      if('' !== secret){
        const { createHmac } = require('crypto');
        let signature = createHmac('sha1', secret).update(body).digest('hex');
        if(req.headers['x-hub-signature'] !== `sha1=${signature}`){
          console.log('Signature Error');
          res.statusCode = 403;
          res.end();
          return;
        }
      }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before application is run, firstly run the following command to add secret variable (STRING can be any string)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;export GITHUB_WEBHOOK_SECRET=STRING&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;After &lt;code&gt;Secret&lt;/code&gt; is set, Github will add &lt;a href="https://developer.github.com/webhooks/#delivery-headers"&gt;header&lt;/a&gt; &lt;code&gt;x-hub-signature&lt;/code&gt; as &lt;code&gt;sha1=SIGNATURE&lt;/code&gt; when request is sent, wherein &lt;code&gt;SIGNATURE&lt;/code&gt; is the HMAC hex digest of body, with key Secret, and algorithm sha1&lt;/li&gt;
&lt;li&gt;Through verification of &lt;code&gt;Secret&lt;/code&gt;, We can make sure that only who know Secret, can send correct request with header &lt;code&gt;x-hub-signature&lt;/code&gt;, or else it will be rejected&lt;/li&gt;
&lt;li&gt;The above code add compatibility for situation when Secret is not set, namely if variable &lt;code&gt;GITHUB_WEBHOOK_SECRET&lt;/code&gt; is not added, the handling logic will be the same as origin, without any verification&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Build via Local Hook
&lt;/h2&gt;

&lt;p&gt;If the project need to be built after pull updates, the building command can be added in the end of variable &lt;code&gt;command&lt;/code&gt;, such as &lt;code&gt;&amp;amp;&amp;amp; npm run build&lt;/code&gt;, but building command of different project may not be the same, furthermore building command of some project can be complicated, git’s local &lt;a href="https://git-scm.com/docs/githooks"&gt;hook&lt;/a&gt; can be set to handle these kind of situation&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;cd /PATH/TO/PROJECT/.git/hooks&lt;br&gt;&lt;br&gt;
nano post-merge&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/sh
SHELL_SCRIPT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;chmod +x post-merge&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Here &lt;code&gt;/PATH/TO/PROJECT/&lt;/code&gt; is the directory location of the project, &lt;code&gt;SHELL_SCRIPT&lt;/code&gt; can be any &lt;code&gt;Shell&lt;/code&gt; script&lt;/li&gt;
&lt;li&gt;Since &lt;a href="https://git-scm.com/docs/git-pull"&gt;git pull&lt;/a&gt; is combination of &lt;code&gt;git fetch&lt;/code&gt; and &lt;code&gt;git merge&lt;/code&gt;, the pull updates will trigger &lt;a href="https://git-scm.com/docs/githooks#_post_merge"&gt;post-merge&lt;/a&gt; hook&lt;/li&gt;
&lt;li&gt;New added file has not execute permission by default, so we need to add &lt;code&gt;x&lt;/code&gt; bit via &lt;code&gt;chmod&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deploy Application Online
&lt;/h2&gt;

&lt;p&gt;Persistence and automation need to be fulfill to deploy application online, namely the project should be always running, and if the server is reboot, the project should run automatically &lt;/p&gt;

&lt;h3&gt;
  
  
  Automatically Create Variable
&lt;/h3&gt;

&lt;p&gt;Script for variable creation in &lt;code&gt;/etc/profile.d/&lt;/code&gt; will run automatically when server reboot, so a setting script is added in it&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;nono /etc/profile.d/github-webhook.sh&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export GITHUB_WEBHOOK_PORT=NUMBER
export GITHUB_WEBHOOK_SECRET=STRING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following command to make the variable creation to take effect at once &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;source /etc/profile&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Run Application via pm2
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://pm2.keymetrics.io/"&gt;pm2&lt;/a&gt; can guarantee sustained running of &lt;code&gt;Node&lt;/code&gt; application, and functionality of monitoring, hot patching and so on can be fulfilled via configuration&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;npm install pm2 -g&lt;br&gt;&lt;br&gt;
pm2 start app.js --name github-webhook &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Automatically Run After Reboot
&lt;/h3&gt;

&lt;p&gt;pm2 has build-in support to config the original application to &lt;a href="https://pm2.keymetrics.io/docs/usage/startup/"&gt;auto-runn when start up&lt;/a&gt;, which can be fulfilled by the following command&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;pm2 startup&lt;br&gt;&lt;br&gt;
pm2 save&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;pm2 startup&lt;/code&gt; will create and enable service which will automatically run when start up, &lt;code&gt;pm2 save&lt;/code&gt; will save the current pm2 running application, as restore content after reboot&lt;/p&gt;

&lt;h2&gt;
  
  
  Summarize
&lt;/h2&gt;

&lt;p&gt;In this automatically deployment process which base on Github webhook, the following technologies have been used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;http&lt;/code&gt;，&lt;code&gt;child_process&lt;/code&gt; and &lt;code&gt;crypto&lt;/code&gt; module of Node.js&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;post-merge&lt;/code&gt; Shell hook of Git&lt;/li&gt;
&lt;li&gt;Automatically variable setting via &lt;code&gt;profile&lt;/code&gt; and &lt;code&gt;pm2&lt;/code&gt; toolkit&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>node</category>
      <category>shell</category>
    </item>
    <item>
      <title>Utilize Nginx to fulfill 301 redirect to root domain of https</title>
      <dc:creator>Chanvin Xiao</dc:creator>
      <pubDate>Sat, 23 May 2020 02:23:42 +0000</pubDate>
      <link>https://forem.com/chanvin/utilize-nginx-to-fulfill-301-redirect-to-root-domain-of-https-141f</link>
      <guid>https://forem.com/chanvin/utilize-nginx-to-fulfill-301-redirect-to-root-domain-of-https-141f</guid>
      <description>&lt;p&gt;Base on consideration of SEO and security, the redirection should be 301, which will be handled generally via Nginx&lt;/p&gt;

&lt;h2&gt;
  
  
  Result to Accomplish
&lt;/h2&gt;

&lt;p&gt;The following address need to be redirected to the root domain of https &lt;a href="https://chanvinxiao.com"&gt;https://chanvinxiao.com&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://chanvinxiao.com"&gt;http://chanvinxiao.com&lt;/a&gt; （http without www）&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://www.chanvinxiao.com"&gt;http://www.chanvinxiao.com&lt;/a&gt; （http with www）&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.chanvinxiao.com"&gt;https://www.chanvinxiao.com&lt;/a&gt; （https with www）&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Different between 301 and 302
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301"&gt;301&lt;/a&gt; is to redirect permanently, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302"&gt;302&lt;/a&gt; is to redirect temporary, and the major distinction is lay in the handling way of search engine&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;301：search engine will transfer weight and PR value&lt;/li&gt;
&lt;li&gt;302：search engine will not handle additionally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now I want search engine treat the original url as non-existence anymore, and completely shift to the new address, so 301 will be used&lt;/p&gt;

&lt;h2&gt;
  
  
  Redirect Http to Https
&lt;/h2&gt;

&lt;p&gt;The simplest way is to &lt;code&gt;return&lt;/code&gt; a redirected address, and add a &lt;code&gt;301&lt;/code&gt; status code in the middle (or else default 302 will be supposed)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
  listen 80;
  return 301 https://$host$request_uri;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#return"&gt;return&lt;/a&gt; and &lt;a href="http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#rewrite"&gt;rewrite&lt;/a&gt; are both rewrite directive of Nginx, since here the path don't need to be changed, &lt;code&gt;return&lt;/code&gt; will be more convenient&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#var_host"&gt;$host&lt;/a&gt; and &lt;a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_uri"&gt;$request_uri&lt;/a&gt; are both Nginx http module's embedded variables, the combination of these two parameters is equivalent to getting rid of &lt;code&gt;http://&lt;/code&gt; for request url&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Www Redirect to Root Domain
&lt;/h2&gt;

&lt;p&gt;Only need to handle in https, because all &lt;code&gt;http&lt;/code&gt; has been redirected to &lt;code&gt;https&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
  listen 443 ssl;
  server_name ~^(?&amp;lt;www&amp;gt;www\.)?(.+)$;
  if ( $www ) {
    return 301 https://$2$request_uri;
  }
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Regular Expression matching of &lt;a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name"&gt;server_name&lt;/a&gt; have been utilized, which can been enabled via adding &lt;code&gt;~&lt;/code&gt; in front of its value, and &lt;a href="http://www.pcre.org"&gt;PCRE&lt;/a&gt; grammar is supported&lt;/li&gt;
&lt;li&gt;Regex usage is to determine if there's prefix &lt;code&gt;www&lt;/code&gt;, and to capture root domain, which will generate two variable, one is &lt;a href="http://www.pcre.org/current/doc/html/pcre2pattern.html#SEC16"&gt;name capture&lt;/a&gt; variable &lt;code&gt;$www&lt;/code&gt;, the other is numeric capture variable $2&lt;/li&gt;
&lt;li&gt;numeric capture variable is not supported in &lt;code&gt;if&lt;/code&gt;, or else there will be error thrown (&lt;code&gt;unknown "1" variable&lt;/code&gt;), that's why &lt;code&gt;?&amp;lt;www&amp;gt;&lt;/code&gt; is added to assign &lt;code&gt;$1&lt;/code&gt; to &lt;code&gt;$www&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reduce Redirect Times
&lt;/h2&gt;

&lt;p&gt;The result is fulfilled by the above settings, but there's a flaw: &lt;a href="http://www.chanvinxiao.com"&gt;http://www.chanvinxiao.com&lt;/a&gt; will redirect to &lt;a href="https://www.chanvinxiao.com"&gt;https://www.chanvinxiao.com&lt;/a&gt; firstly, then redirect again to &lt;a href="https://chanvinxiao.com"&gt;https://chanvinxiao.com&lt;/a&gt;. redirecting twice is definitely not as good as redirecting once, so one-stop would be better, and the http config will be modified as follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
  listen 80;
  server_name ~^(?:www\.)?(.+)$;
  return 301 https://$1$request_uri;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In the corresponding server of http, &lt;code&gt;server_name&lt;/code&gt; also is changed to regex mode, and $host is substituted with captured root domain &lt;code&gt;$1&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;Www will be removed directly, so it's not necessary to capture, use &lt;code&gt;?:&lt;/code&gt; symbol to fulfill &lt;a href="http://www.pcre.org/current/doc/html/pcre2pattern.html#SEC14"&gt;grouping&lt;/a&gt; without capturing, so the following root domain becomes &lt;code&gt;$1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Thus the result is that no matter there is www or not, it will be redirected to https domain without www&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;There is not need to appoint specific domain in the above config, so it will be convenient to be compatible and migrate, and the following feature of Nginx is used: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Regular expression matching of &lt;code&gt;server_name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;return&lt;/code&gt; directive which receive status code and address&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$host&lt;/code&gt; and &lt;code&gt;$request_uri&lt;/code&gt; embedded variables&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nginx</category>
      <category>http</category>
      <category>regex</category>
    </item>
    <item>
      <title>Value as Attribute and Property with Vue.js</title>
      <dc:creator>Chanvin Xiao</dc:creator>
      <pubDate>Sun, 17 May 2020 01:49:01 +0000</pubDate>
      <link>https://forem.com/chanvin/value-as-attribute-and-property-with-vue-js-3pj1</link>
      <guid>https://forem.com/chanvin/value-as-attribute-and-property-with-vue-js-3pj1</guid>
      <description>&lt;p&gt;Attribute and property are confusing concepts in web development, and for value, because of its particularity, are more easy to puzzle us, I'm trying to distinguish them with examples&lt;/p&gt;

&lt;h2&gt;
  
  
  Concept of Attribute and Property
&lt;/h2&gt;

&lt;p&gt;To put it simplely, attribute is for element tag, and property is for element object, such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;input id="input" value="test value"&amp;gt;
&amp;lt;script&amp;gt;
let input = document.getElementById('input');
console.log(input.getAttribute('value')); // test value
console.log(input.value); // test value
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Value attribute of input is defined through &lt;code&gt;value="test value"&lt;/code&gt; of tag, and can be gotten via &lt;code&gt;input.getAttribute('value')&lt;/code&gt;, and can be updated via &lt;code&gt;input.setAttribute('value', 'New Value')&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Value property of input can be gotten and updated via &lt;code&gt;input.value&lt;/code&gt;，and the initial value is the same as attribute&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Binding of Attribute and Property
&lt;/h2&gt;

&lt;p&gt;At the very beginning, if the attribute value is updated, the property value will be changed too.&lt;/p&gt;

&lt;p&gt;But if property value is updated (input in the input element or assign new value to &lt;code&gt;input.value&lt;/code&gt;), the attribute value will not be changed, further more, if attribute value is updated again now, the property value will not be changed with it, such as this &lt;a href="https://chanvinxiao.com/cn/blog/value-as-attribute-and-property-with-vuejs/value.gif"&gt;animation&lt;/a&gt; shown, and you can also try to operate in this &lt;a href="https://codepen.io/chanvin/pen/RwPzdMd?editors=1010"&gt;page&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Actually, the &lt;a href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-dirty"&gt;dirty value flag&lt;/a&gt; is taking effect. the initial value of &lt;code&gt;dirty value flag&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt;, namely update of attribute value will be changed, but once property value is updated by user interaction, value of dirty value flag will become true, namely update of attribute value will not affect corresponding property value&lt;/p&gt;

&lt;p&gt;So in the real world project, we're generally handling value as property&lt;/p&gt;

&lt;h2&gt;
  
  
  Value handled by Vue.js
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Usually use :value
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;v-bind&lt;/code&gt; of Vue.js, is handling attribute normally, if need to be handled as property, we need to add &lt;code&gt;.prop&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;v-bind:value&lt;/code&gt; are mostly handling property value by default, because it's transformed forcibly&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;input id="input" :value="'test value'" &amp;gt;
&amp;lt;script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script&amp;gt;
let input = new Vue({
  el: '#input',
  mounted () {
    console.log(this.$el.getAttribute('value')); // null
    console.log(this.$el.value); // test value
    console.log(this._vnode.data) // {attrs: {id: "input"}, domProps: {value: "test value"}}
  }
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visible, Vue.js treat value as domProps of VNode's data, not attrs, so it will become value as property after mounted&lt;/p&gt;

&lt;p&gt;In the source code of Vue.js, the handling of forcibly transfer property is as following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/compiler/parser/index.js
function processAttrs (el) {
...
        if ((modifiers &amp;amp;&amp;amp; modifiers.prop) || (
          !el.component &amp;amp;&amp;amp; platformMustUseProp(el.tag, el.attrsMap.type, name)
        )) {
          addProp(el, name, value, list[i], isDynamic)
        } else {
          addAttr(el, name, value, list[i], isDynamic)
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;inside it, definition of &lt;code&gt;platformMustUseProp&lt;/code&gt; in web platform is as following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/platforms/web/util/attrs.js
const acceptValue = makeMap('input,textarea,option,select,progress')
export const mustUseProp = (tag: string, type: ?string, attr: string): boolean =&amp;gt; {
  return (
    (attr === 'value' &amp;amp;&amp;amp; acceptValue(tag)) &amp;amp;&amp;amp; type !== 'button' ||
    (attr === 'selected' &amp;amp;&amp;amp; tag === 'option') ||
    (attr === 'checked' &amp;amp;&amp;amp; tag === 'input') ||
    (attr === 'muted' &amp;amp;&amp;amp; tag === 'video')
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the foregoing, value of input whose type is not button, textarea, option, select and progress will forcibly to be property, without the need to set &lt;code&gt;:value.prop&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For example, &lt;a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/textarea"&gt;textarea&lt;/a&gt; tab does not support value attribute, so the value of following code will not shown in the textarea&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;textarea value="test value"&amp;gt;&amp;lt;/textarea&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But in Vue.js, the following code can successfully bind to value property and show in the textarea&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;textarea :value="'test value'"&amp;gt;&amp;lt;/textarea&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explicitly use :value.prop
&lt;/h3&gt;

&lt;p&gt;One more thing deserves attention in the above Vue.js source code is that, to be treated forcibly as property, &lt;code&gt;!el.component&lt;/code&gt; need to be met, namely it's not a dynamical component, because el.component of dynamical component will be its &lt;code&gt;is&lt;/code&gt; attribute value&lt;/p&gt;

&lt;p&gt;Namely &lt;code&gt;v-bind&lt;/code&gt; of dynamical component will be treated as attribute, if need to be as property, the &lt;code&gt;.prop&lt;/code&gt; need to be added, such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div id="app"&amp;gt;
  &amp;lt;component :is="element" :value.prop="'test value'"&amp;gt;&amp;lt;/component&amp;gt;
  &amp;lt;button @click="change"&amp;gt;Change&amp;lt;/button&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script&amp;gt;
let app = new Vue({
  el: '#app',
  data: {
    element: 'input'
  },
  methods: {
    change () {
      this.element = 'input' === this.element ? 'textarea' : 'input';
    }
  }
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If in the above &lt;code&gt;component&lt;/code&gt;, substitute &lt;code&gt;:value.prop&lt;/code&gt; with &lt;code&gt;.prop&lt;/code&gt;, when switch to &lt;code&gt;textarea&lt;/code&gt;, the value will not show in the textarea, you can click to switch tab to see it in this &lt;a href="https://codepen.io/chanvin/pen/KKpjYmy?editors=1010"&gt;page&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The binding relationship of value as attribute and property will invalid after property value update by user interaction&lt;/li&gt;
&lt;li&gt;Vue.js usually use &lt;code&gt;:value&lt;/code&gt; to treat value as property&lt;/li&gt;
&lt;li&gt;Dynamic component of Vue.js need to use &lt;code&gt;:value.prop&lt;/code&gt; to treat value as property&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>html</category>
      <category>javascript</category>
      <category>vue</category>
    </item>
    <item>
      <title>Use Vue.js to rewrite React's Official Tutorial Tic Tac Toe</title>
      <dc:creator>Chanvin Xiao</dc:creator>
      <pubDate>Tue, 05 May 2020 01:36:55 +0000</pubDate>
      <link>https://forem.com/chanvin/use-vue-js-to-rewrite-react-s-official-tutorial-tic-tac-toe-84d</link>
      <guid>https://forem.com/chanvin/use-vue-js-to-rewrite-react-s-official-tutorial-tic-tac-toe-84d</guid>
      <description>&lt;p&gt;React's official &lt;a href="https://reactjs.org/tutorial/tutorial.html"&gt;tutorial&lt;/a&gt; tic tac toe do a good job to guide the newbie to enter React's world step by step, I think that similar tutorial will inspire Vue.js's newbie, so I use Vue.js to rewrite it&lt;/p&gt;

&lt;p&gt;First you can see the final &lt;a href="https://codepen.io/chanvin/pen/rNVZwJy?editors=0010"&gt;result&lt;/a&gt;, and try to click and experience, we will fulfill this effect gradually&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial Code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Initial Effect
&lt;/h3&gt;

&lt;p&gt;Open &lt;a href="https://codepen.io/chanvin/pen/yLNxVdL"&gt;Initial status&lt;/a&gt; and edit directly, or copy the code to corresponding files in the same directory&lt;br&gt;&lt;br&gt;
For now it's just a simple tic tac toe grid, and a hard-coded "Next Player"&lt;/p&gt;
&lt;h3&gt;
  
  
  Initial Code Description
&lt;/h3&gt;

&lt;p&gt;Now three component has been defined, which are &lt;code&gt;Square&lt;/code&gt;, &lt;code&gt;Board&lt;/code&gt; and &lt;code&gt;Game&lt;/code&gt; respectively&lt;/p&gt;

&lt;p&gt;Square is just a normal button now&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vue.component('Square', {
  template: `
    &amp;lt;button class="square"&amp;gt;
      {{ /* TODO */ }}
    &amp;lt;/button&amp;gt;
  `
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;After component is defined like this, other component can use &amp;lt;Square /&amp;gt; to reference this component directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Board component is composed by current status and 9 Square&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vue.component('Board', {
  data() {
    return {
      status: `${nextLabel}X`,
      board: [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8]
      ]
    }
  },
  template: `
    &amp;lt;div&amp;gt;
      &amp;lt;div class="status"&amp;gt;{{ status }}&amp;lt;/div&amp;gt;
      &amp;lt;div class="board-row" v-for="(row, index) in board" :key="index"&amp;gt;
        &amp;lt;Square v-for="square in row" :key="square" /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  `
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;the current &lt;code&gt;status&lt;/code&gt; and value of &lt;code&gt;board&lt;/code&gt; is defined in &lt;code&gt;data&lt;/code&gt;, thus you can use &lt;code&gt;{{ status }}&lt;/code&gt; to reference the value of status, and use &lt;code&gt;v-for&lt;/code&gt; to iterate two dimension array &lt;code&gt;board&lt;/code&gt; twice to compose tic tac toe grid
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data&lt;/code&gt; in component must be a function which return a object, but not literal object&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;v-for&lt;/code&gt; must have &lt;code&gt;key&lt;/code&gt; to make sure performance without alert&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;Game&lt;/code&gt; component is formed by &lt;code&gt;Board&lt;/code&gt;, and status and history which will be added later&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vue.component('Game', {
  template: `
    &amp;lt;div class="game"&amp;gt;
      &amp;lt;div class="game-board"&amp;gt;
        &amp;lt;Board /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div class="game-info"&amp;gt;
        &amp;lt;div&amp;gt;{{ /* status */ }}&amp;lt;/div&amp;gt;
        &amp;lt;ol&amp;gt;{{ /* TODO */ }}&amp;lt;/ol&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  `
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add Data Handling
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Add Props
&lt;/h3&gt;

&lt;p&gt;Deliver a &lt;code&gt;prop&lt;/code&gt; whose name is &lt;code&gt;value&lt;/code&gt; to Square in Board&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Square v-for="square in row" :key="square" :value="square" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;:value is short for v-bind:value, which means that its value is an expression&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add &lt;code&gt;value&lt;/code&gt; prop in the component definition and template of Square&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vue.component('Square', {
  props: ['value'],
  template: `
    &amp;lt;button class="square"&amp;gt;
      {{ value }}
    &amp;lt;/button&amp;gt;
  `
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;props&lt;/code&gt; are variables which parent component can deliver to child component, set corresponding attribute in tag when parent component invoke child component, and the usage method is the same as &lt;code&gt;data&lt;/code&gt; in child component &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Current &lt;a href="https://codepen.io/chanvin/pen/wvaEgPV?editors=0010"&gt;code and effect&lt;/a&gt;: number 0 - 8 are filled into the tic tac toe respectively &lt;/p&gt;

&lt;h3&gt;
  
  
  Add Interactive
&lt;/h3&gt;

&lt;p&gt;Add click event to button element to update value&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vue.component('Square', {
  //props: ['value'],
  data() {
    return {
      value: null
    }
  },
  methods: {
    setValue() {
      this.value = 'X';
    }
  },
  template: `
    &amp;lt;button class="square" @click="setValue"&amp;gt;
      {{ value }}
    &amp;lt;/button&amp;gt;
  `
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@click&lt;/code&gt; is short for &lt;code&gt;v-on:click&lt;/code&gt;, whose value is the function which will run when click, here set to setValue which is defined in methods of the component&lt;/li&gt;
&lt;li&gt;Child component can not update data of parent directly, so change value from props to data&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data&lt;/code&gt;'s value will be updated, and corresponding template will update automatically to display the content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Current &lt;a href="https://codepen.io/chanvin/pen/jOPvyxW?editors=0010"&gt;Code and Effect&lt;/a&gt;: click the tic tac toe grip, the cell will be filled by X&lt;/p&gt;

&lt;h2&gt;
  
  
  Improve Game
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Data Upward
&lt;/h3&gt;

&lt;p&gt;To alternatively play and confirm winner, we need to determine status of every cell uniformly, so the value will be lifted to Board&lt;/p&gt;

&lt;p&gt;Add data &lt;code&gt;squares&lt;/code&gt; and method &lt;code&gt;handleClick&lt;/code&gt; to Board&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vue.component('Board', {
  data() {
    return {
      ...
      squares: Array(9).fill(null),
    }
  },
  methods: {
    handleClick(i) {
      const squares = this.squares.slice();
      if (squares[i]){
        alert('Place was taken!');
        return
      }
      squares[i] = 'X';
      this.squares = squares;
    }
  },
  template: `
    ...
      &amp;lt;div class="board-row" v-for="(row, index) in board" :key="index"&amp;gt;
        &amp;lt;Square v-for="square in row" :key="square" :value="squares[square]" @click="handleClick(square)" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Init &lt;code&gt;squares&lt;/code&gt; to a array with 9 null, so the tic tac toe grip will be empty&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;handleClick&lt;/code&gt; accepts parameter of corresponding cell number, and will update corresponding &lt;code&gt;square&lt;/code&gt; element&lt;/li&gt;
&lt;li&gt;the event handler is not the return value of  &lt;code&gt;handleClick(square)&lt;/code&gt;, but &lt;code&gt;handleClick&lt;/code&gt;, and &lt;code&gt;square&lt;/code&gt; will be parameter when trigger&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trigger click event of Board in the click event handler of Square&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vue.component('Square', {
  props: ['value'],
  methods: {
    setValue() {
      this.$emit('click');
    }
  },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;value&lt;/code&gt; need to be changed from data back to &lt;code&gt;props&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$emit&lt;/code&gt; can invoke event handler which parent component deliver&lt;/li&gt;
&lt;li&gt;value of prop is updated in parent component, and the child template will update display content correspondingly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Current &lt;a href="https://codepen.io/chanvin/pen/mdJGWxV?editors=0010"&gt;Code and Effect&lt;/a&gt;: click the tic tac toe grid, if it's not taken, it will be filled with X&lt;/p&gt;

&lt;h3&gt;
  
  
  Play Alternatively
&lt;/h3&gt;

&lt;p&gt;Add data &lt;code&gt;xIsNext&lt;/code&gt;, and switch when click&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data() {
  return {
    ...
    xIsNext: true
  }
},
methods: {
    handleClick(i) {
      ...
      squares[i] = this.xIsNext ? 'X' : 'O';
      this.squares = squares;
      this.xIsNext = !this.xIsNext;
      this.status = `${nextLabel}${this.xIsNext ? 'X' : 'O'}`;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Init &lt;code&gt;xIsNext&lt;/code&gt; as &lt;code&gt;true&lt;/code&gt;, which means X will be first player
&lt;/li&gt;
&lt;li&gt;After click, reverse xIsNext to switch&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;status&lt;/code&gt; to the next player&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;current &lt;a href="https://codepen.io/chanvin/pen/ZEGMKBZ?editors=0010"&gt;code and effect&lt;/a&gt;: click the tic tac toe grid, X and O will play alternatively&lt;/p&gt;

&lt;h3&gt;
  
  
  Determine Winner
&lt;/h3&gt;

&lt;p&gt;Add function the calculate winner&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function calculateWinner(squares) {
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];
  for (let i = 0; i &amp;lt; lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] &amp;amp;&amp;amp; squares[a] === squares[b] &amp;amp;&amp;amp; squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Enumerate the combination which will win the game, and compare with value of &lt;code&gt;squares&lt;/code&gt; array&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add winner logic of click handler function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (calculateWinner(squares)) {
  alert('Winner was determined!');
  return;
}
...
const winner = calculateWinner(squares);
if (winner) {
  this.status = 'Winner: ' + winner;
  return;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;After click, if there's winner before, than the clicking is invalid&lt;/li&gt;
&lt;li&gt;After placement proceeding, judge winner again, and update status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Current &lt;a href="https://codepen.io/chanvin/pen/PoqdmEQ?editors=0010"&gt;code and effect&lt;/a&gt;: status and click handler will be updated after one side win&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Time Tour
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Save History Record
&lt;/h3&gt;

&lt;p&gt;To fulfill functionality of retraction, we need to record entire status of every placement, equivalent to the snapshot of the chessboard, which will became a history record, upward to the &lt;code&gt;Game&lt;/code&gt; component&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;history&lt;/code&gt; data in &lt;code&gt;Game&lt;/code&gt;, transfer &lt;code&gt;xIsNext&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt; and &lt;code&gt;handleClick&lt;/code&gt; method from Board to Game&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vue.component('Game', {
  data() {
    return {
      history: [{
        squares: Array(9).fill(null),
      }],
      xIsNext: true,
      status: `${nextLabel}X`
    }
  },
  methods: {
    handleClick(i) {
      const history = this.history;
      const current = history[history.length - 1]
      const squares = current.squares.slice();
      ...
      squares[i] = this.xIsNext ? 'X' : 'O';
      history.push({
        squares: squares
      });
      ...
    }
  },
  template: `
    &amp;lt;div class="game"&amp;gt;
      &amp;lt;div class="game-board"&amp;gt;
        &amp;lt;Board :squares="history[history.length - 1].squares" @click="handleClick" /&amp;gt;
  `
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Utilize the last record of history to assign value to squares (only one record for now)&lt;/li&gt;
&lt;li&gt;After placement, squares will record the placement, and history will add a record&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add prop squares to Board, and update handleClick to invoke event handler of parent component&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vue.component('Board', {
  props: ['squares'],
  methods: {
    handleClick(i) {
      this.$emit('click', i);
    }
  },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Current &lt;a href="https://codepen.io/chanvin/pen/poJOwbv?editors=0010"&gt;code and effect&lt;/a&gt;: status location is updated, and store history is recorded&lt;/p&gt;

&lt;h3&gt;
  
  
  Show History Step Record
&lt;/h3&gt;

&lt;p&gt;Iterate history records to display, and bind click event, show record of corresponding step via update of &lt;code&gt;stepNumber&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vue.component('Game', {
  data() {
    ...
      stepNumber: 0,
    ...
    }
  },
  methods: {
    handleClick(i) {
      const history = this.history.slice(0, this.stepNumber + 1);
      ...
      this.history = history.concat([{
        squares: squares
      }]);
      this.stepNumber = history.length;
      ...
    },
    jumpTo(step) {
      if(step === this.stepNumber){
        alert('Already at ' + (0 === step ? 'Beginning' : `Step#${step}!`));
        return;
      }
      this.stepNumber = step;
      this.xIsNext = (step % 2) === 0;
      this.status = `${nextLabel}${this.xIsNext ? 'X' : 'O'}`;
    }
  },
  template: `
    &amp;lt;div class="game"&amp;gt;
      &amp;lt;div class="game-board"&amp;gt;
        &amp;lt;Board :squares="history[this.stepNumber].squares" @click="handleClick" /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div class="game-info"&amp;gt;
        &amp;lt;div&amp;gt;{{ status }}&amp;lt;/div&amp;gt;
        &amp;lt;ol&amp;gt;
          &amp;lt;li v-for="(squares, index) in history" :key="index" :class="{'move-on': index === stepNumber}"&amp;gt;
            &amp;lt;button @click="jumpTo(index)"&amp;gt;{{ 0 === index ? 'Go to start' : 'Go to move#' + index }}&amp;lt;/button&amp;gt;
   ...
  `
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;stepNumber&lt;/code&gt; in Game, and init it as &lt;code&gt;0&lt;/code&gt;, record current display step&lt;/li&gt;
&lt;li&gt;Utilize corresponding step of &lt;code&gt;this.stepNumber&lt;/code&gt; to assign value to prop &lt;code&gt;squares&lt;/code&gt; of Board &lt;/li&gt;
&lt;li&gt;Handle &lt;code&gt;history&lt;/code&gt; with current step as foundation in handleClick, and update stepNumber&lt;/li&gt;
&lt;li&gt;Add method &lt;code&gt;jumpTo&lt;/code&gt; to handle display of going back to history, update &lt;code&gt;stepNumber&lt;/code&gt;, &lt;code&gt;xIsNext&lt;/code&gt; and &lt;code&gt;status&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Current &lt;a href="https://codepen.io/chanvin/pen/rNVZwJy?editors=0010"&gt;code and effect&lt;/a&gt;: there will be one more history step after every placement, and click the step can return to this step&lt;/p&gt;

&lt;h2&gt;
  
  
  Summarize
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Game Accomplishment
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Place cell alternatively&lt;/li&gt;
&lt;li&gt;Determine Winner&lt;/li&gt;
&lt;li&gt;Retract and play again&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Technology Showcase
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;v-bind: bind data in template&lt;/li&gt;
&lt;li&gt;v-for: Iterate array in template&lt;/li&gt;
&lt;li&gt;v-on, $emit: transfer and trigger event between components&lt;/li&gt;
&lt;li&gt;data: define in component and automatically update in template&lt;/li&gt;
&lt;li&gt;prop: transfer between components and automatically update in template &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vue</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
