如何使用 vue-cli 3 的 preset 打造基於 git repo 的前端專案模板
TLDR
背景介紹
vue-cli 3 完全推翻了 vue-cli 2 的整體架構設計,所以當你需要給組裡定製一份基於 vue-cli 的前端專案初始化模板時,就需要去思考:我該怎麼做?
我們要做的事情很簡單,就是當別人使用 vue create xxx
命令初始化一個前端專案時,可以從 git repo 去拉取專案初始化資訊,好處有兩點:
- 團隊內部所有的新專案都是統一的目錄結構和程式碼組織方式,便於維護
- 後期可以開發公共外掛服務於不同的專案,提高效率
因為 vue-cli 3 才出來不久,所以探索的過程中踩了很多坑,這裡就來總結下。
整體設計
vue-cli 官網介紹到:
你可以通過釋出 git repo 將一個 preset 分享給其他開發者。這個 repo 應該包含以下檔案:
- preset.json: 包含 preset 資料的主要檔案(必需)。
- generator.js: 一個可以注入或是修改專案中檔案的 Generator。
- prompts.js: 一個可以通過命令列對話為 generator 收集選項的 prompts 檔案。
# 從 GitHub repo 使用 preset
vue create --preset username/repo my-project
GitLab 和 BitBucket 也是支援的。如果要從私有 repo 獲取,請確保使用 --clone 選項:
vue create --preset gitlab:username/repo --clone my-project vue create --preset bitbucket:username/repo --clone my-project
是不是看上去很簡單,起碼我在實踐過程中還是遇到了一些問題,接下來就重點講下。
git repo 引數
上面 --preset
後跟的引數 username/repo
實際是下圖中的紅框內部分(千萬別以為是 git clone
後的地址):
preset.json 檔案
先說一點:當你直接用 vue create xxx
初始化專案時,如果你將初始化資訊儲存成一個本地模板後,會寫入到你係統的 ~/.vuerc
檔案中。該檔案中的內容其實就是我們接下來需要配置的 present.json
。
此處直接 show code :
{ "useConfigFiles": true, "cssPreprocessor": "less", "plugins": { "@vue/cli-plugin-babel": { "version": "^3.0.0" }, "@vue/cli-plugin-eslint": { "version": "^3.0.0", "config": "recommended", "lintOn": ["save", "commit"] } }, "configs": { "vue": { "baseUrl": "/", "outputDir": "dist", "assetsDir": "static", "filenameHashing": true, "lintOnSave": true, "runtimeCompiler": false, "transpileDependencies": [], "productionSourceMap": false, "pages": { "index": { "entry": "src/main.js", "template": "public/index.html", "filename": "index.html", "title": "首頁", "chunks": ["chunk-vendors", "chunk-common", "index"] } }, "devServer": { "open": true, "host": "127.0.0.1", "https": false, "hotOnly": false, "proxy": null }, "pwa": {}, "pluginOptions": {} }, "postcss": {}, "eslintConfig": { } }, "router": true, "vuex": false, "routerHistoryMode": false }
其中當 "useConfigFiles": true
時, configs
內的配置資訊會直接覆蓋初始化後項目中的 vue.config.js
。
prompts.js 檔案
prompts.js 其實就是你在初始化專案時,系統會詢問你的配置選項問題,比如你的專案需不需要安裝 vuex
? 需不需要安裝 vue-router
?
你的回答會直接影響後面初始化生成的專案檔案。
這裡最需要注意一點!!!
當你檢視官方文件時,第一眼看到就是下圖:
只要你這樣寫,就一定會 報錯 !!!
原因很簡單:上圖中 prompts.js
的寫法是開發基於 vue-cli-service
外掛的程式碼。而當你是要開發專案模板時,正確寫法如下:
module.exports = [
{
name: "vuex",
type: "confirm",
message: `是否需要使用 vuex`,
default: false
},
{
name: "elementUI",
type: "confirm",
message: `element-ui`,
default: false
}
];
這一點其實官網也有提到,只是很不容易注意到。
此處再給大家安利下 vue-cli-plugin-vuetify 這個開源外掛中 prompts.js
的寫法。程式猿嘛,最愛的就是栗子。
generator.js 檔案
接下來就是 generator.js
,這個檔案負責的就是 注入或是修改專案中檔案。
同樣,我還是直接 show code :
module.exports = (api, options, rootOptions) => {
// 安裝一些基礎公共庫
api.extendPackage({
dependencies: {
"axios": "^0.18.0",
"lodash": "^4.17.10",
"keymirror": "^0.1.1"
},
devDependencies: {
"mockjs": "^1.0.1-beta3"
}
});
// 安裝 vuex
if (options.vuex) {
api.extendPackage({
dependencies: {
vuex: '^3.0.1'
}
});
api.render('./template/vuex');
}
// 安裝 element-ui 庫
if (options.elementUI) {
api.extendPackage({
devDependencies: {
"element-ui": "^2.4.6"
}
});
}
// 公共基礎目錄和檔案
api.render('./template/default');
// 配置檔案
api.render({
'./.eslintrc.js' : './template/_eslintrc.js',
'./.gitignore' : './template/_gitignore',
'./.postcssrc.js' : './template/_postcssrc.js'
});
}
核心 api:
-
api.extendPackage
: 負責給初始化專案中的package.json
新增額外依賴並安裝; -
api.render
: 負責將模板專案中提前定義好的目錄和檔案拷貝到初始化的專案中;
對於 api.render
需要注意幾點:
- 拷貝目錄的話,直接傳地址字串,
render
函式會將你所傳目錄內的所有檔案覆蓋初始化專案中src
目錄下的檔案(我的測試結果是限於兩層目錄); - 拷貝檔案的話,直接傳入一個
object
,其中key
對應初始化專案中的目標位置,value
對應模板專案中的檔案位置; - 當你需要建立一個以
.
開頭的檔案時,模板專案中需要用_
替代.
,這點官網有說明;
最後再說個很重要點,vue-cli 3 在拷貝檔案時使用的是 EJS
模板去實現的,所以開發者是可以在任意檔案中使用 EJS
語法去做更細粒度的控制。比如我的 main.js
:
import Vue from 'vue'
import App from './App.vue'
<%_ if (options.vuex) { _%>
import store from './store'
<%_ } _%>
<%_ if (options.elementUI) { _%>
import ElementUI from 'element-ui';
Vue.use(ElementUI);
<%_ } _%>
// simulation data
import './mock/index';
Vue.config.productionTip = false
new Vue({
router,
<%_ if (options.vuex) { _%>
store,
<%_ } _%>
render: h => h(App)
}).$mount('#app')
其中 options.vuex
和 options.elementUI
就是使用者在處理 prompts.js
中設定的問題的回答值。正是基於這點,我沒有再去使用 api.postProcessFiles
這個 api 。
今天就寫到這裡,後續有補充再寫~