構建基于Git的靜態全棧式框架 - CMS、移動應用、桌面編輯器

jopen 9年前發布 | 29K 次閱讀 Git 版本控制系統

或許你也用過Hexo / Jekyll / Octopress這樣的靜態博客,他們的原理都是類似的。我們有一個代碼庫用于生成靜態頁面,然后這些靜態頁面會被PUSH到Github Pages上。

從我們設計系統的角度來說,我們會在Github上有三個主要代碼庫:

  1. Content。用于存放編輯器生成的JSON文件,這樣我們就可以GET這些資源,并用Backbone / Angular / React 這些前端框架來搭建SPA。
  2. Code。開發者在這里存放他們的代碼,如主題、靜態文件生成器、資源文件等等。
  3. Builder。在這里它是運行于Travis CI上的一些腳本文件,用于Clone代碼,并執行Code中的腳本。
  4. </ol>

    以及一些額外的服務,當且僅當你有一些額外的功能需求的時候。

    1. Extend Service。當我們需要搜索服務時,我們就需要這樣的一些服務。如我正考慮使用Python的whoosh來完成這個功能,這時候我計劃用Flask框架,但是只是計劃中——因為沒有合適的中間件。
    2. Editor。相比于前面的那些知識這一步適合更重要,也就是為什么生成的格式是JSON而不是Markdown的原理。對于非程序員來說,要熟練掌握Markdown不是一件容易的事。于是,一個考慮中的方案就是使用 Electron + Node.js來生成API,最后通過GitHub API V3來實現上傳。
    3. Mobile App。
    4. </ol>

      So,這一個過程是如何進行的。

      用戶場景

      整個過程的Pipeline如下所示:

      1. 編輯使用他們的編輯器來編輯的內容并點擊發布,然后這個內容就可以通過GitHub API上傳到Content這個Repo里。
      2. 這時候需要有一個WebHooks監測到了Content代碼庫的變化,便運行Builder這個代碼庫的Travis CI。
      3. 這個Builder腳本首先,會設置一些基本的git配置。然后clone Content和Code的代碼,接著運行構建命令,生成新的內容。
      4. 然后Builder Commit內容,并PUSH內容。
      5. </ol>

        在這種情形中,編輯能否完成工作就不依賴于網站——脫稿又少了 個借口。這時候網站出錯的概率太小了——你不需要一個緩存服務器、HTTP服務器,由于沒有動態生成的內容,你也不需要守護進程。這些內容都是靜態文件,你可以將他們放在任何可以提供靜態文件托管的地方——CloudFront、S3等等。或者你再相信自己的服務器,Nginx可是全球第二好(第一還沒出現)的靜態文件服務器。

        開發人員只在需要的時候去修改網站的一些內容。So,你可能會擔心如果這時候修改的東西有問題了怎么辦。

        1. 使用這種模式就意味著你需要有測試來覆蓋這些構建工具、生成工具。
        2. 相比于自己的代碼,別人的CMS更可靠?
        3. </ol>

          需要注意的是如果你上一次構建成功,你生成的文件都是正常的,那么你只需要回滾開發相關的代碼即可。舊的代碼仍然可以工作得很好。其次,由于生成的是靜態文件,查錯的成本就比較低。最后,重新放上之前的靜態文件。

          Code: 生成靜態頁面

          Assemble是一個使用Node.js,Grunt.js,Gulp,Yeoman 等來實現的靜態網頁生成系統。這樣的生成器有很多,Zurb Foundation, Zurb Ink, Less.js / lesscss.org, Topcoat, Web Experience Toolkit等組織都使用這個工具來生成。這個工具似乎上個Release在一年多以前,現在正在開始0.6。雖然,這并不重要,但是還是順便一說。

          我們所要做的就是在我們的Gruntfile.js中寫相應的生成代碼。

              assemble: {
                options: {
                  flatten: true,
                  partials: ['templates/includes/*.hbs'],
                  layoutdir: 'templates/layouts',
                  data: 'content/blogs.json',
                  layout: 'default.hbs'
                },
                site: {
                  files: {'dest/': ['templates/*.hbs']}
                },
                blogs: {
                  options: {
                    flatten: true,
                    layoutdir: 'templates/layouts',
                    data: 'content/*.json',
                    partials: ['templates/includes/*.hbs'],
                    pages: pages
                  },
                  files: [
                    { dest: './dest/blog/', src: '!*' }
                  ]
                }
              }

          配置中的site用于生成頁面相關的內容,blogs則可以根據json文件的文件名生成對就的html文件存儲到blog目錄中。

          生成后的目錄結果如下圖所示:

           .
          ├── about.html
          ├── blog
          │   ├── blog-posts.html
          │   └── blogs.html
          ├── blog.html
          ├── css
          │   ├── images
          │   │   └── banner.jpg
          │   └── style.css
          ├── index.html
          └── js
              ├── jquery.min.js
              └── script.js

          7 directories, 30 files</pre>

          這里的靜態文件內容就是最后我們要發布的內容。

          還需要做的一件事情就是:

          grunt.registerTask('dev', ['default', 'connect:server', 'watch:site']);

          用于開發階段這樣的代碼就夠了,這個和你使用WebPack + React 似乎相差不了多少。

          Builder: 構建生成工具

          Github與Travis之間,可以做一個自動部署的工具。相信已經有很多人在Github上玩過這樣的東西——先在Github上生成Token,然后用travis加密:

          travis encrypt-file ssh_key --add

          加密后的Key就會保存到.travis.yml文件里,然后就可以在Travis CI上push你的代碼到Github上了。

          接著,你需要創建個deploy腳本,并且在after_success執行它:

          after_success: - test $TRAVIS_PULL_REQUEST == "false" && test $TRAVIS_BRANCH == "master" && bash deploy.sh

          在這個腳本里,你所需要做的就是clone content和code中的代碼,并執行code中的生成腳本,生成新的內容后,提交代碼。

          #!/bin/bash

          set -o errexit -o nounset

          rev=$(git rev-parse --short HEAD)

          cd stage/

          git init git config user.name "Robot" git config user.email "robot@phodal.com"

          git remote add upstream "https://$GH_TOKEN@github.com/phodal-archive/echeveria-deploy.git" git fetch upstream git reset upstream/gh-pages

          git clone https://github.com/phodal-archive/echeveria-deploy code git clone https://github.com/phodal-archive/echeveria-content content pwd cp -a content/contents code/content

          cd code

          npm install npm install grunt-cli -g grunt mv dest/* ../ cd ../ rm -rf code rm -rf content

          touch .

          if [ ! -f CNAME ]; then echo "deploy.baimizhou.net" > CNAME fi

          git add -A . git commit -m "rebuild pages at ${rev}" git push -q upstream HEAD:gh-pages</pre>

          這就是這個builder做的事情——其中最主要的一個任務是grunt,它所做的就是:

          grunt.registerTask('default', ['clean', 'assemble', 'copy']);

          Content:JSON格式

          在使用Github和Travis CI完成Content的時候,發現沒有一個好的Webhook。雖然我們的Content只能存儲一些數據,但是放一個trigger腳本也是可以原諒的。

          var Travis = require('travis-ci');

          var repo = "phodal-archive/echeveria-deploy";

          var travis = new Travis({ version: '2.0.0' });

          travis.authenticate({ github_token: process.env.GH_TOKEN

          }, function (err, res) { if (err) { return console.error(err); }

          travis.repos(repo.split('/')[0], repo.split('/')[1]).builds.get(function (err, res) {
              if (err) {
                  return console.error(err);
              }
          
              travis.requests.post({
                  build_id: res.builds[0].id
              }, function (err, res) {
                  if (err) {
                      return console.error(err);
                  }
                  console.log(res.flash[0].notice);
              });
          });
          

          });</pre>

          這里主要依賴于Travis CI來完成這部分功能,這時候我們還需要數據。

          從Schema到數據庫

          我們在我們數據庫中定義好了Schema——對一個數據庫的結構描述。在《編輯-發布-開發分離 》一文中我們說到了echeveria-content的一個數據文件如下所示:

            {
              "title": "白米粥",
              "author": "白米粥",
              "url": "baimizhou",
              "date": "2015-10-21",
              "description": "# Blog post \n  > This is an example blog post \n Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ",
              "blogpost": "# Blog post \n  > This is an example blog post \n Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \n Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
            }

          比起之前的直接生成靜態頁面這里的數據就是更有意思地一步了,我們從數據庫讀取數據就是為了生成一個JSON文件。何不直接以JSON的形式存儲文件呢?

          我們都定義了這每篇文章的基本元素:

          1. title
          2. author
          3. date
          4. description
          5. content
          6. url
          7. </ol>

            即使我們使用NoSQL我們也很難逃離這種模式。我們定義這些數據,為了在使用的時候更方便。存儲這些數據只是這個過程中的一部分,下部分就是取出這些數據并對他們進行過濾,取出我們需要的數據。

            Web的骨架就是這么簡單,當然APP也是如此。難的地方在于存儲怎樣的數據,返回怎樣的數據。不同的網站存儲著不同的數據,如淘寶存儲的是商品的信息,Google存儲著各種網站的數據——人們需要不同的方式去存儲這些數據,為了更好地存儲衍生了更多的數據存儲方案——于是有了GFS、 Haystack等等。運營型網站想盡辦法為最后一公里努力著,成長型的網站一直在想著怎樣更好的返回數據,從更好的用戶體驗到機器學習。而數據則是這個過程中不變的東西。

            盡管,我已經想了很多辦法去盡可能減少元素——在最開始的版本里只有標題和內容。然而為了滿足我們在數據庫中定義的結構,不得不造出來這么多對于一般用戶不友好的字段。如鏈接名是為了存儲的文件名而存在的,即這個鏈接名在最后會變成文件名:

            repo.write('master', 'contents/' + data.url + '.json', stringifyData, 'Robot: add article ' + data.title, options, function (err, data) {
                  if(data.commit){
                    that.setState({message: "上傳成功" + JSON.stringify(data)});
                    that.refs.snackbar.show();
                    that.setState({
                      sending: 0
                    });
                  }
                });

            然后,上面的數據就會變成一個對象存儲到“數據庫”中。

            今天 ,仍然有很多人用Word、Excel來存儲數據。因為對于他們來說,這些軟件更為直接,他們簡單地操作一下就可以對數據進行排序、篩選。數據以怎樣的形式存儲并不重要,重要的是他們都以文件的形式存儲著。

            git作為NoSQL數據庫

            不同的數據庫會以不同的形式存儲到文件中去。blob是git中最為基本的存儲單位,我們的每個content都是一個blob。redis可以以rdb文件的形式存儲到文件系統中。完成一個CMS,我們并不需要那么多的查詢功能。

            這些上千年的組織機構,只想讓人們知道他們想要說的東西。

            </blockquote>

            我們使用NoSQL是因為:

            1. 不使用關系模型
            2. 在集群中運行良好
            3. 開源
            4. 無模式
            5. 數據交換格式
            6. </ol>

              我想其中只有兩點對于我來說是比較重要的集群與數據格式。但是集群和數據格式都不是我們要考慮的問題。。。

              我們也不存在數據格式的問題、開源的問題,什么問題都沒有。。除了,我們之前說到的查詢——但是這是可以解決的問題,我們甚至可以返回不同的歷史版本的。在這一點上git做得很好,他不會像WordPress那樣存儲多個版本。

              JSON文件 + Nginx就可以變成這樣一個合理的API,甚至是運行方式。我們可以對其進行增、刪、改、查,盡管就當前來說查需要一個額外的軟件來執行,但是為了實現一個用得比較少的功能,而去花費大把的時間可能就是在浪費。

              git的“API”提供了豐富的增、刪、改功能——你需要commit就可以了。我們所要做的就是:

              1. git commit
              2. git push
              3. </ol>

                于是,就會有一個很忙的Travis-Github Robot在默默地為你工作。

                Robot提交代碼

                一鍵發布:編輯器

                為了實現之前說到的編輯-發布-開發分離的CMS,我還是花了兩天的時間打造了一個面向普通用戶的編輯器。效果截圖如下所示:

                編輯器

                作為一個普通用戶,這是一個很簡單的軟件。除了Electron + Node.js + React作了一個140M左右的軟件,盡管壓縮完只有40M左右 ,但是還是會把用戶嚇跑的。不過作為一個快速構建的原型已經很不錯了——構建速度很快、并且運行良好。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!