版本說明
$ vue --version
@vue/cli 4.5.9
$ node --version
v14.0.0
$ npm --version
7.6.1
原始碼位置-mac
/usr/local/lib/node_modules/@vue/cli
流程說明
- 根據
package.json
中的bin
可以知道入口檔案在bin/vue.js
vue.js
中找到create <app-name>
的命令設定,在action
鉤子中呼叫了lib/create
create.js
- 獲取專案絕對路徑
- 判斷專案資料夾是否存在
- 建立資料夾
- 呼叫
lib/Creator.js
Creator.js
Creator.create()
-
根據建立專案時的選擇項配置
preset
外掛組 -
深度拷貝
preset
-
配置
preset
中的預設外掛的配置項:@vue/cli-service
、@vue/cli-plugin-router
、@vue/cli-plugin-typescript
、@vue/cli-plugin-vuex
-
獲取包管理器:
yarn/npm/pnpm
-
開始建立專案,獲取
vue-cli
版本 -
根據
preset
中的外掛配置生成package.json
檔案 -
為
pnpm
生成.npmrc
檔案,為yarn
生成.yarnrc
-
為專案新增git配置:
git init
-
安裝CLI外掛:生成指定格式的檔案目錄
await generator.generate({ extractConfigFiles: preset.useConfigFiles, });
-
喚醒外掛
-
安裝配置額外的依賴
-
running completion hooks
-
生成
README.md
-
配置git的狀態,為測試配置Git
-
完成
自定義修改腳手架程式
公司內專案所使用的外掛等配置基本相似,在專案開發過程中也有很多提高開發效率的做法,這些可以在腳手架的程式流程中自定義
- 擺脫複製-貼上的重複工作,同時避免複製-貼上丟失導致的可以不用debug的bug
- 提高專案開發效率,統一專案開發風格
生成vue.config.js-仿生成README.md
- generateVueConfig.js
// 生成README.md:lib/util/generateReademe.js
// 生成vue.config.js:lib/util/generateVueConfig.js,我另有其他配置項,需要根據配置生成vue.config.js
module.exports = function generateVueConfig(plugins) {
const iconSvg = plugins['svg-sprite-loader'] ? `chainWebpack: config => {
// svg rule loader
const svgRule = config.module.rule('svg') // 找到svg-loader
svgRule.uses.clear() // 清除已有的loader, 如果不這樣做會新增在此loader之後
svgRule.exclude.add(/node_modules/) // 正則匹配排除node_modules目錄
svgRule // 新增svg新的loader處理
.test(/\.svg$/)
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]',
})
// 修改images loader 新增svg處理
const imagesRule = config.module.rule('images')
imagesRule.exclude.add(resolve('src/icon/svg'))
config.module
.rule('images')
.test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
}` : ''
return `const path = require('path')
function resolve(dir) {
return path.join(__dirname, './', dir)
}
module.exports = {
publicPath: './',
devServer: {
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true
}
}
},
productionSourceMap: false,
${iconSvg}
}`
}
-
修改create流程-Creator.js
// 自定義生成vue.config.js,寫入多次使用的配置,比如跨域配置,可以直接寫,也可以將內容寫在另一個js檔案中並引入 if (!generator.files['vue.config.js']) { log() log('⚙\u{fe0f} Generating vue.config.js...') await writeFileTree(context, { 'vue.config.js': generateVueConfig(preset.otherPlugins), }) }
配置並引入自定義npm包-仿配置@vue/cli-xxx包
在開發中,有一些包總會被引入,比如axios,加密的包,UI框架包等,可以在vue create前期選擇時將其加入,來生成程式碼。
引入@vue/cli-xxx包流程:
- create.js中,根據
new Creator
來進入整體流程,初始化Creator時,傳入了初始包列表,以下僅摘要了重要程式碼
// create.js
const { getPromptModules } = require('./util/createTools')
const creator = new Creator(name, targetDir, getPromptModules())
// getPromptModules()
exports.getPromptModules = () => {
return [
'vueVersion',
'babel',
'typescript',
'pwa',
'router',
'vuex',
'cssPreprocessors',
'linter',
'unit',
'e2e'
].map(file => require(`../promptModules/${file}`))
}
- 初始化Creator時,呼叫了
PromptModuleAPI
引入了相關包,呼叫了相關包的配置命令lib/promptModules/xxx
// Creator.js
constructor(name, context, promptModules) {
const promptAPI = new PromptModuleAPI(this);
promptModules.forEach((m) => m(promptAPI));
// 以上命令執行後,在shell介面中會顯示每個相關包的配置命令,此時使用者進行選擇或輸入
}
- 在Creator.create()方法中,根據preset為每一個包配置了配置項,儲存在
preset.plugins
中
// Creator.js-create()方法,以下僅舉例
preset.plugins["@vue/cli-service"] = Object.assign(
{
projectName: name,
},
preset
);
if (cliOptions.bare) {
preset.plugins["@vue/cli-service"].bare = true;
}
// legacy support for router
if (preset.router) {
preset.plugins["@vue/cli-plugin-router"] = {};
if (preset.routerHistoryMode) {
preset.plugins["@vue/cli-plugin-router"].historyMode = true;
}
}
仿製以上流程即可匯入自己想在初始化過程中匯入的包,比如axios等,為避免衝突,全部為另開發的程式碼,以下以axios以及可選擇式的UI框架來舉例
- createOtherTools.js -仿製getPromptModules函式,配置自定義包列表,並新增到初始化Creator中
// lib/util/createOtherTools.js otherModules資料夾儲存自定義包的相關命令列
exports.getOtherPromptModules = () => {
return [
'axios',
'uiStruct'
].map(file => require(`../otherModules/${file}`))
}
// create.js
const { getPromptModules } = require('./util/createTools')
const { getOtherPromptModules } = require('./util/createOtherTools')
const creator = new Creator(name, targetDir, getPromptModules(), getOtherPromptModules())
- 匯入相關包的命令列配置,將自定義包的配置儲存在
options.otherPlugins
中
// Creator.js
constructor(name, context, promptModules, otherPromptModules) {
const promptAPI = new PromptModuleAPI(this);
promptModules.forEach((m) => m(promptAPI));
otherPromptModules.forEach((m) => m(promptAPI));
// 以上命令執行後,在shell介面中會顯示每個相關包的配置命令,此時使用者進行選擇或輸入
}
// 新建 otherModules 資料夾
// otherModules/axios.js
module.exports = cli => {
cli.injectFeature({
name: 'Axios', // 顯示在vue create命令後的選擇項裡的名字
value: 'axios', // 相對應的value值
description: 'Promise based HTTP client for the browser and node.js', // 介紹
link: 'https://github.com/mzabriskie/axios', // 相關連結
checked: true // 顯示在vue create命令後的選擇項裡,是否預設選中
})
cli.onPromptComplete((answers, options) => {
if (answers.features.includes('axios')) { // includes(value)是否包含上面定義的value值
options.otherPlugins['axios'] = {} // 在此指定相對應的報名
}
})
}
// otherModules/uiStruct.js
module.exports = cli => {
cli.injectFeature({
name: 'Choose UI Struct', // 顯示在vue create命令後的選擇項裡的名字
value: 'ui-struct', // 相對應的value值
description: 'Choose a struct of UI that you want to use with the project' // 介紹
})
cli.injectPrompt({
name: 'uiStruct',
when: answers => answers.features.includes('ui-struct'), // 判斷是否選擇
message: 'Choose a struct of UI that you want to use with the project', // 描述
type: 'list', // 選擇類shell命令
choices: [
{
name: 'ant-design-vue', // 選項名
value: 'ant-design-vue' // 選項相對應的包名
},
{
name: 'element-ui',
value: 'element-ui'
}
],
default: 'element-ui' // 預設選項
})
cli.onPromptComplete((answers, options) => {
if (answers.uiStruct) {
options.otherPlugins[answers.uiStruct] = {} // answers.uiStruct儲存了包名
}
})
}
- 將自定義包的引入加入到流程中,在生成package.json之前引入
// Creator.js - create()函式
if (preset.otherPlugins) {
Object.keys(preset.otherPlugins).forEach((dep) => {
let { version } = preset.otherPlugins[dep];
pkg.dependencies[dep] = version ? version : "latest";
});
}