本文系原創,轉載請附帶作者資訊:yhlben
前言
在實際的開發過程中,從零開始初始化一個專案往往很麻煩,所以各種各樣的腳手架工具應運而生。crea-react-app,vue-cli,@angular/cli 等腳手架工具,只需要執行一個命令,專案結構以及開發環境就搭建好了。
腳手架工具確實方便了我們使用,開發者可以專注於業務,而不需要考慮太多的環境搭建。但作者認為,學習腳手架工具背後的搭建過程也是很重要的,以防腳手架掛了之後,我們還能正常搭建專案。基於這個目的,作者從零搭建了cdfang-spider專案。
現在讓我們就以這個專案為例,從零開始搭建專案吧。
專案選型
-
三大框架裡選哪個?
- react 個人愛好。
- react-router 定義路由。
- react context 狀態管理。
- react hooks 元件化。
-
引入強型別語言?
- typescript。為 js 提供型別支援,編輯器友好,增加程式碼可維護性,使用起來心裡踏實。
- 在使用第三方庫時,可以寫出更加符合規範的程式碼,避免 api 亂用等。
- 專案中依賴了大量 @types/xxx 包,無形中增加了專案體積。
- 編輯器對 ts 檔案進行型別檢查,需要遍歷 node_modules 目錄下所有的 @types 檔案,會造成編輯器卡頓現象。
- 目前仍然存在很多庫沒有 @types 支援,使用起來並不方便。
-
css 選型?
- 預編譯器 less。專案中使用了變數定義,選擇器巢狀,選擇器複用等,less 夠用了。
- 解決命名衝突可以使用 css modules,暫未考慮 css in js。
- 使用 bem 命名規範。
- 使用 postcss 外掛 autoprefixer,增加 css 相容性。
-
構建工具選哪個?
- webpack。內建 tree shaking,scope hosting 等,打包效率高,社群活躍。
- webpack-merge 合併不同環境配置檔案。
- 配置 externals。引入 cdn 代替 node_modules 中體積較大的包。
- gulp。用來打包 node 端程式碼。
-
程式碼規範檢查?
- eslint。輔助編碼規範執行,有效控制程式碼質量。同時也支援校驗 typescript 語法。
- 配置 eslint-config-airbnb 規則。
- 配置 eslint-config-prettier 關閉和 prettier 衝突的規則。
-
測試框架選型?
- jest。大而全,包含:測試框架,斷言庫,mock 資料,覆蓋率等。
- enzyme。測試 react 元件。
-
後端框架選型?
- koa。精簡好用,中介軟體機制強大。
- apollo-server。幫助搭建 graphQL 後端環境。
-
資料庫選型?
- mongodb。類 json 的存錯格式,方便儲存,前端友好。
- 配置 mongoose,方便給 mongodb 資料庫建模。
-
介面方式選型?
- graphql。可以根據需要格式獲取對應資料,減少介面冗餘資料。
- graphql schema 定義了後端介面的引數,操作和返回型別,從此不需要提供介面文件。
- 前端可以在 schema 定義後開始開發,資料格式自己掌握。
- schema 可拼接。可以組合和連線多個 graphql api,進行級聯查詢等。
- 社群友好,有很多優秀的庫可以直接使用: apollo,relay 等。
基本框架選型完畢,接下來就開始搭建專案環境。
搭建 TypeScript 環境
TypeScript 是 JavaScript 的超集,意味著可以完全相容 JavaScript 檔案,但 TypeScript 檔案卻並不能直接在瀏覽器中執行,需要經過編譯生成 JavaScript 檔案後才能執行。
1、 新建 tsconfig.json 檔案。
- tsc -init 生成初始化 tsconfig.json 檔案。
- vscode 會根據 tsconfig.json 檔案,進行動態型別檢查,語法錯誤提示等。
- tsc 命令會根據 tsconfig.json 檔案配置的規則,將 ts 程式碼轉換為 js 程式碼。
- tslint 會讀取 tsconfig.json 檔案中的規則,輔助編碼規範校驗。
- tslint 官宣會被廢棄,後將被 eslint 代替。
- eslint 同樣會用到 tsconfig.json 檔案中的內容。
2、 配置 eslint。
根據 typescript-eslint 引導,配置 eslint 對 typescript 的支援。
- @typescript-eslint/parser 解析 ts 語法。
- @typescript-eslint/eslint-plugin 為 ts 檔案應用 eslint 和 tslint 規則。
3、 選擇一個 typescript 編譯器,tsc 還是 babel?
使用 babel。好處如下:
- babel 社群有許多非常好的外掛,babel-preset-env 可以支援到具體相容瀏覽器的版本號,而 tsc 編譯器沒這個功能。
- babel 可以同時支援編譯 js 和 ts,所以沒必要在引入 tsc 編譯 ts 檔案,只管理一個編譯器,可維護性更高。
- babel 編譯速度更快。tsc 編譯器需要遍歷所有型別定義檔案(*.d.ts),包括 node_modules 裡的,以確保程式碼中正確地使用,type 太多會造成卡頓。
babel 流程分析
babel 是一個 js 語法編譯器,在編譯時分為 3 個階段:解析、轉換、輸出。
- 解析階段:將 js 程式碼解析為抽象語法樹(ast)。
- 轉換階段:對 ast 進行修改,產生一個轉換後的 ast。
- 輸出階段:將轉換後的 ast 輸出成 js 檔案。
plugin 和 preset
- plugin: 解析,轉換,並輸出轉換後的 js 檔案。例如:@babel/plugin-proposal-object-rest-spread 會輸出支援
{...}
解構語法的 js 檔案。- preset: 是一組組合好的 plugin 集合。例如:@babel/preset-env 讓程式碼支援最新的 es 語法,自動引入需要支援新特性的 plugin。
4、蒐集所有的 ts,tsx 頁面(前端環境使用 webpack,node 專案使用 gulp),然後通過 babel 編譯成 js 檔案。
搭建 React 環境
React 是一個庫,基於元件式開發,開發時常常需要用到以下語法:
- es6 模組化。
- jsx 語法。
- typescript 語法。
- css 前處理器。
這些語法在目前瀏覽器中並不能直接執行,需要進行打包編譯,這也是搭建 React 環境的主要工作。
具體步驟
1、新建一個 html 檔案,並在 body 中建立一個根節點,用於掛載 react 最後生成的 dom。
2、新建一個 index.tsx 檔案,用於將專案中的所有元件,引入進來,並呼叫 render 方法,將元件渲染到根節點中。
3、React 專案分層。
- containers 目錄,存放單獨的頁面
- components 目錄,存放的是元件,一個元件包含 jsx 和 css 兩個部分。
- context 目錄,存放公用的 react context。
- config 目錄,存放公共配置檔案。
- utils 目錄,公用的函式元件庫。
- constants 目錄,存放靜態變數。
4、配置 webpack,以 index.tsx 為入口檔案,進行打包編譯。
- 由於不同環境的打包方式並不相同,這裡抽象出開發環境、上線環境、優化環境的配置檔案,使用 webpack-merge 合併配置檔案。
- 配置 css 前處理器,使用 less-loader。
- 配置 ts 編譯器,使用 babel-loader。
- @babel/preset-env:編譯最新的 es 語法。
- @babel/preset-react:編譯 react 語法。
- @babel/preset-typescript:轉換 typescript 語法。
- 配置 url-loader,打包專案中的圖片資源。
- 配置 html-webpack-plugin 將最後生成的 js,css,注入第 1 步的 html 中。
- 使用 ejs 模板配置開發環境和線上環境引入的 cdn。
- 開發環境配置,使用開箱即用的 webpack-dev-server。
- webpack-dev-server 可以自動監聽檔案修改,自動重新整理頁面,以及預設 source-map 等功能。
- 配置熱模組替換,react-hot-loader。
webpack 打包原理
webpack 打包過程就像是一條流水線,從入口檔案開始,蒐集專案中所有檔案的依賴關係,如果遇到不能夠識別的模組,就使用對應的 loader 轉換成能夠識別的模組。webpack 還能使用 plugin 在流水線生命週期中掛載自定義事件,來控制 webpack 輸出結果。
5、編寫 npm script,一鍵開啟開發模式。
// cross-env 用來跨環境設定環境變數
"scripts": {
"dev:client": "cross-env NODE_ENV=development webpack-dev-server --open"
}
複製程式碼
6、現在執行 npm run dev:client
就可以愉快地編寫客戶端程式碼了。
搭建 NodeJs 環境
由於 node 端使用了 typescript 和最新的 es 語法,所以需要進行打包編譯。
- 配置 gulp,遍歷每一個 ts 檔案,呼叫 gulp-babel,將 ts 程式碼轉換成 js 程式碼。
- 配置 supervisor 自動重啟 node 服務(nodemon 對於不存在的目錄不能進行監控)。
- 編寫 npm script 一鍵啟動 node 端開發環境。
"scripts": {
"dev:server": "cross-env NODE_ENV=development gulp & cross-env NODE_ENV=development supervisor -i ./dist/client/ -w ./dist/ ./dist/app.js",
}
複製程式碼
配置好 gulp 後,就可以執行 npm run dev:server
一鍵啟動伺服器端開發環境。
層次結構劃分
專案採用傳統的 mvc 模式進行層次劃分。
Model 層
Model 層的主要工作:連線資料庫,封裝資料庫操作,例如:新增資料、刪除資料、查詢資料、更新資料等。
- 新建 model 資料夾,目錄下的每一個檔案對應資料庫的一個表。
- model 檔案中包含對一個資料表的增刪改查操作。
- 使用 mongoose 更方便地對 mongodb 資料庫進行讀寫操作。
- model 檔案返回封裝好的物件,提供給 controller 層使用。
Controller 層
Controller 層的主要工作:接收和傳送 http 請求。根據前端請求,呼叫 model 層獲取資料,再返回給前端。
傳統的後端一般還包含 service 層,專門用來處理業務邏輯。
- 根據前端請求,找到對應的 model 層獲取資料,經過加工處理後,返回給前端。
- 編寫中介軟體,記錄系統日誌,錯誤處理,404 頁面等。
- 支援前端 react-router 中的 BrowserRouter。根據前端路由,後端配置對應的路由,匹配結果為 index.html 檔案。
- 專案中使用的 graphql 比較基礎,也直接放在了 controller 層進行處理。
View 層
View 層的主要工作:提供前端頁面模板。如果是伺服器端渲染,是將 model 層的資料注入到 view 層中,最後通過 controller 層返回給客戶端。由於本專案前端使用 react 渲染,所以 view 層直接是經過 webpack 打包後的頁面。
- 使用 koa-static 提供一個靜態檔案伺服器,用來訪問前端打包後生成的 html 檔案。
搭建 GraphQL 環境
GraphQL 是一種用於 api 的查詢語言,需要伺服器端配置 graphql 支援,同時也需要客戶端使用 graphql 語法的格式進行請求。
使用 apollo 更快的搭建 graphql 環境。
- 伺服器端配置 apollo-server。
- 使用 schema,定義請求的型別,返回的格式。
- 使用 resolvers 來處理對應的 schema。
- 客戶端配置 apollo-client。
- 按照 apollo-server 定義的 schema,來請求資料。
搭建 MongoDB 環境
MongoDB 是一個面向文件儲存的資料庫,操作起來十分簡單。
Mongoose 為 mongodb 提供了一種直接的,基於 scheme 結構去定義你的資料模型。它內建資料驗證,查詢構建,業務邏輯鉤子等,開箱即用。
- 使用 mongoose 建立和本地 mongodb 的連線。
- 建立 model 模型,一個模型對應 mongodb 裡的一張表。
- 根據 model 封裝增刪改查功能,並返回給 controller 層使用。
接下來的步驟就是安裝 mongodb,啟動服務,就可以了。
搭建測試環境
本專案使用 jest 作為測試框架,jest 包含了斷言庫、測試框架、mock 資料等功能,是一個大而全的測試庫。由於前端使用了 react 專案,這裡引入了專門用來測試 react 的 enzyme 庫。
1、新建 jest.config.js 檔案。
- 配置初始化 setup.ts 檔案。
- 根據 react 版本配置對應的 enzyme-adapter。
- mock 全域性變數,如 fech,canvas 等。
- 配置需要測試的檔案。
- 配置 mock 資料檔案。
- 配置測試檔案的編譯方式。
- ts 程式碼使用 ts-jest 編譯。
- 配置程式碼覆蓋率檔案。
2、編寫測試檔案。
- 新建__mocks__,__tests__目錄,存放測試檔案和 mock 資料檔案。
- 按照 src 中的目錄,建立相應的測試檔案目錄。
3、編寫測試指令碼和上傳覆蓋率指令碼。
"scripts": {
"test": "jest --no-cache --colors --coverage --forceExit --detectOpenHandles",
"coverage": "codecov"
}
複製程式碼
配置上線環境
安裝好各種環境之後,接下來就要考慮專案上線了。
配置伺服器環境
- 安裝 nodejs 環境。nvm 安裝 node
- 安裝 pm2 程式守護。
npm i pm2 -g
- 安裝 mongodb。mongodb 官方文件
- 安裝免費 https 證照。letsencrypt 官網
- 域名需要先進行備案(使用阿里雲備案,資料準備齊全的話 10 天左右就可以批下來)。
程式碼釋出
本專案釋出非常簡單,只需要一步操作就搞定了,這些都是經過持續整合配置後的結果。
# clone with Git Bash
git clone https://github.com/yhlben/cdfang-spider.git
# change directory
cd cdfang-spider
# install dependencies
npm i
# build for production with minification
npm run build
複製程式碼
所有的事情都在 build 命令下完成了,我們分析一下 npm run build 命令做的事情。
- eslint 語法錯誤檢查。
- 單元測試。
- 上傳測試覆蓋率。
- 打包客戶端程式碼。
- 打包後生成 html 檔案作為 node 端的 view 層,和後端繫結在一起。
- 其他靜態資源,在 webpack 打包後自動上傳到七牛 cdn,使用 qiniu-upload-plugin 來進行一鍵上傳。
- 打包伺服器端程式碼。
上述事情通過建立 npm script 就可以了完成需求了,但這些命令也不應該每次都由手工敲一遍,通過配置 travisCI,每一次 master 分支提交程式碼時,自動執行上述命令就行了。
travisCI 配置
travisCI 是一個持續整合平臺,每當 github 提交程式碼時,travisCI 就會得到通知,然後根據 travisCI 中的配置資訊執行相應的操作,並及時把執行結果反饋給使用者。travisCI 配置檔案可以參考專案根目錄下的 .travis.yml
檔案。配置檔案核心在於 script 的配置。
script:
- npm run build
- npm run test
after_success: npm run coverage
複製程式碼
可以看到,每一次 github 提交後,travisCI 就會執行 名稱為 build 的任務,任務分為 2 個步驟,首先執行 build 命令,然後執行 test 命令,當命令都執行完成後,執行 coverage 命令。如果執行命令期間出現任何錯誤,travisCI 會通過郵件及時通知我們。真正要上線時,先檢視 ci 狀態,如果已通過所有的步驟,那就不用擔心釋出的程式碼有問題了。
總結
至此,整個專案選型與搭建流程已經介紹完畢了,當然還有一些很細節的地方沒有寫進去,如果有不太明白的地方,可以提 issue,或者加我微信 yhl2016226。
接下來對以下 4 個方面寫個小總結。
- 開發方面:專案將前端、後端、資料庫端連通起來,組合成了一個小全棧的專案,加深了我對整個開發環節的理解。
- 測試方面:通過編寫單元測試,ui 測試,api 測試,積累了自動化測試方面的經驗。
- 運維方面:通過配置持續整合,守護程式,nginx,https 等,讓我有能力實現小型專案的部署。
- 技術方面:專案中使用了一些比較新的技術,如:hooks api,graphql 等,但用的都很基礎,主要是為了練手,後續還得深入學習。
對於專案後期更新,主要是基於以下幾個方面:graphql,docker,k8s,微服務,serverless 等,東西太多,還得加油學習啊,?