vue create 初步解析以及定制化修改


版本說明

$ 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

流程說明

  1. 根據package.json中的bin可以知道入口文件在bin/vue.js
  2. vue.js中找到create <app-name>的命令設定,在action鈎子中調用了lib/create

create.js

  1. 獲取項目絕對路徑
  2. 判斷項目文件夾是否存在
  3. 創建文件夾
  4. 調用lib/Creator.js

Creator.js

Creator.create()

  1. 根據創建項目時的選擇項配置preset插件組

  2. 深度拷貝preset

  3. 配置preset中的默認插件的配置項:@vue/cli-service@vue/cli-plugin-router@vue/cli-plugin-typescript@vue/cli-plugin-vuex

  4. 獲取包管理器:yarn/npm/pnpm

  5. 開始創建項目,獲取vue-cli版本

  6. 根據preset中的插件配置生成package.json文件

  7. pnpm生成.npmrc文件,為yarn生成.yarnrc

  8. 為項目添加git配置:git init

  9. 安裝CLI插件:生成指定格式的文件目錄

    await generator.generate({
      extractConfigFiles: preset.useConfigFiles,
    });
    
  10. 喚醒插件

  11. 安裝配置額外的依賴

  12. running completion hooks

  13. 生成README.md

  14. 配置git的狀態,為測試配置Git

  15. 完成

自定義修改腳手架程序

公司內項目所使用的插件等配置基本相似,在項目開發過程中也有很多提高開發效率的做法,這些可以在腳手架的程序流程中自定義

  1. 擺脫復制-粘貼的重復工作,同時避免復制-粘貼丟失導致的可以不用debug的bug
  2. 提高項目開發效率,統一項目開發風格

生成vue.config.js-仿生成README.md

  1. 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}
}`
}
  1. 修改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包流程:

  1. 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}`))
}

  1. 初始化Creator時,調用了PromptModuleAPI引入了相關包,調用了相關包的配置命令lib/promptModules/xxx
// Creator.js
constructor(name, context, promptModules) {
  const promptAPI = new PromptModuleAPI(this);
  promptModules.forEach((m) => m(promptAPI));
  // 以上命令執行后,在shell界面中會顯示每個相關包的配置命令,此時使用者進行選擇或輸入
}
  1. 在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框架來舉例

  1. 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())
  1. 導入相關包的命令行配置,將自定義包的配置存儲在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存儲了包名
    }
  })
}

  1. 將自定義包的引入加入到流程中,在生成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";
  });
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM