通过Vue CLI能够不便的创立一个Vue我的项目,然而对于理论我的项目来说还是不够的,所以个别都会依据业务的状况来在其根底上增加一些共性能力,缩小创立新我的项目时的一些反复操作,本着学习和分享的目标,本文会介绍一下咱们Vue我的项目的前端架构设计,当然,有些中央可能不是最好的形式,毕竟大家的业务不尽相同,适宜你的就是最好的。

除了介绍根本的架构设计,本文还会介绍如何开发一个Vue CLI插件和preset预设。

ps.本文基于Vue2.x版本,node版本16.5.0

创立一个根本我的项目

先应用Vue CLI创立一个根本的我的项目:

vue create hello-world

而后抉择Vue2选项创立,初始我的项目构造如下:

接下来就在此基础上添砖加瓦。

路由

路由是必不可少的,装置vue-router

npm install vue-router

批改App.vue文件:

<template>  <div id="app">    <router-view />  </div></template><script>export default {  name: 'App',}</script><style>* {  padding: 0;  margin: 0;  border: 0;  outline: none;}html,body {  width: 100%;  height: 100%;}</style><style scoped>#app {  width: 100%;  height: 100%;  display: flex;}</style>

减少路由进口,简略设置了一下页面款式。

接下来新增pages目录用于搁置页面, 把本来App.vue的内容移到了Hello.vue

路由配置咱们抉择基于文件进行配置,在src目录下新建一个/src/router.config.js

export default [  {    path: '/',    redirect: '/hello',  },  {    name: 'hello',    path: '/hello/',    component: 'Hello',  }]

属性反对vue-router构建选项routes的所有属性,component属性传的是pages目录下的组件门路,规定路由组件只能放到pages目录下,而后新建一个/src/router.js文件:

import Vue from 'vue'import Router from 'vue-router'import routes from './router.config.js'Vue.use(Router)const createRoute = (routes) => {    if (!routes) {        return []    }    return routes.map((item) => {        return {            ...item,            component: () => {                return import('./pages/' + item.component)            },            children: createRoute(item.children)        }    })}const router = new Router({    mode: 'history',    routes: createRoute(routes),})export default router

应用工厂函数和import办法来定义动静组件,须要递归对子路由进行解决。最初,在main.js外面引入路由:

// main.js// ...import router from './router'// ++// ...new Vue({  router,// ++  render: h => h(App),}).$mount('#app')

菜单

咱们的业务基本上都须要一个菜单,默认显示在页面左侧,咱们有外部的组件库,但没有对外开源,所以本文就应用Element代替,菜单也通过文件来配置,新建/src/nav.config.js文件:

export default [{    title: 'hello',    router: '/hello',    icon: 'el-icon-menu'}]

而后批改App.vue文件:

<template>  <div id="app">    <el-menu      style="width: 250px; height: 100%"      :router="true"      :default-active="defaultActive"    >      <el-menu-item        v-for="(item, index) in navList"        :key="index"        :index="item.router"      >        <i :class="item.icon"></i>        <span slot="title">{{ item.title }}</span>      </el-menu-item>    </el-menu>    <router-view />  </div></template><script>import navList from './nav.config.js'export default {  name: 'App',  data() {    return {      navList,    }  },  computed: {    defaultActive() {      let path = this.$route.path      // 查看是否有齐全匹配的      let fullMatch = navList.find((item) => {        return item.router === path      })      // 没有则查看是否有局部匹配      if (!fullMatch) {        fullMatch = navList.find((item) => {          return new RegExp('^' + item.router + '/').test(path)        })      }      return fullMatch ? fullMatch.router : ''    },  },}</script>

成果如下:

当然,上述只是意思一下,理论的要简单一些,毕竟这里连嵌套菜单的状况都没思考。

权限

咱们的权限颗粒度比拟大,只管制到路由层面,具体实现就是在菜单配置和路由配置里的每一项都新增一个code字段,而后通过申请获取以后用户有权限的code,没有权限的菜单默认不显示,拜访没有权限的路由会重定向到403页面。

获取权限数据

权限数据随用户信息接口一起返回,而后存储到vuex里,所以先配置一下vuex,装置:

npm install vuex --save

新增/src/store.js

import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({    state: {        userInfo: null,    },    actions: {        // 申请用户信息        async getUserInfo(ctx) {            let userInfo = {                // ...                code: ['001'] // 用户领有的权限            }            ctx.commit('setUserInfo', userInfo)        }    },    mutations: {        setUserInfo(state, userInfo) {            state.userInfo = userInfo        }    },})

main.js外面先获取用户信息,而后再初始化Vue

// ...import store from './store'// ...const initApp = async () => {  await store.dispatch('getUserInfo')  new Vue({    router,    store,    render: h => h(App),  }).$mount('#app')}initApp()

菜单

批改nav.config.js新增code字段:

// nav.config.jsexport default [{    title: 'hello',    router: '/hello',    icon: 'el-icon-menu'    code: '001',}]

而后在App.vue里过滤掉没有权限的菜单:

export default {  name: 'App',  data() {    return {      navList,// --    }  },  computed: {    navList() {// ++      const { userInfo } = this.$store.state      if (!userInfo || !userInfo.code || userInfo.code.length <= 0) return []      return navList.filter((item) => {        return userInfo.code.includes(item.code)      })    }  }}

这样没有权限的菜单就不会显示进去。

路由

批改router.config.js,减少code字段:

export default [{        path: '/',        redirect: '/hello',    },    {        name: 'hello',        path: '/hello/',        component: 'Hello',        code: '001',    }]

code是自定义字段,须要保留到路由记录的meta字段里,否则最初会失落,批改createRoute办法:

// router.js// ...const createRoute = (routes) => {    // ...    return routes.map((item) => {        return {            ...item,            component: () => {                return import('./pages/' + item.component)            },            children: createRoute(item.children),            meta: {// ++                code: item.code            }        }    })}// ...

而后须要拦挡路由跳转,判断是否有权限,没有权限就转到403页面:

// router.js// ...import store from './store'// ...router.beforeEach((to, from, next) => {    const userInfo = store.state.userInfo    const code = userInfo && userInfo.code && userInfo.code.length > 0 ? userInfo.code : []    // 去谬误页面间接跳转即可,否则会引起死循环    if (/^\/error\//.test(to.path)) {        return next()    }    // 有权限间接跳转    if (code.includes(to.meta.code)) {        next()    } else if (to.meta.code) { // 路由存在,没有权限,跳转到403页面        next({            path: '/error/403'        })    } else { // 没有code则代表是非法门路,跳转到404页面        next({            path: '/error/404'        })    }})

error组件还没有,新增一下:

// pages/Error.vue<template>  <div class="container">{{ errorText }}</div></template><script>const map = {  403: '无权限',  404: '页面不存在',}export default {  name: 'Error',  computed: {    errorText() {      return map[this.$route.params.type] || '未知谬误'    },  },}</script><style scoped>.container {    width: 100%;    height: 100%;    display: flex;    justify-content: center;    align-items: center;    font-size: 50px;}</style>

接下来批改一下router.config.js,减少谬误页面的路由,及减少一个测试无权限的路由:

// router.config.jsexport default [    // ...    {        name: 'Error',        path: '/error/:type',        component: 'Error',    },    {        name: 'hi',        path: '/hi/',        code: '无权限测试,请输出hi',        component: 'Hello',    }]

因为这个code用户并没有,所以当初咱们关上/hi路由会间接跳转到403路由:

面包屑

和菜单相似,面包屑也是大部分页面都须要的,面包屑的组成分为两局部,一部分是在以后菜单中的地位,另一部分是在页面操作中产生的门路。第一局部的门路因为可能会动静的变动,所以个别是通过接口随用户信息一起获取,而后存到vuex里,批改store.js

// ...async getUserInfo(ctx) {    let userInfo = {        code: ['001'],        breadcrumb: {// 减少面包屑数据            '001': ['你好'],        },    }    ctx.commit('setUserInfo', userInfo)}// ...

第二局部的在router.config.js外面配置:

export default [    //...    {        name: 'hello',        path: '/hello/',        component: 'Hello',        code: '001',        breadcrumb: ['世界'],// ++    }]

breadcrumb字段和code字段一样,属于自定义字段,然而这个字段的数据是给组件应用的,组件须要获取这个字段的数据而后在页面上渲染出面包屑菜单,所以保留到meta字段上尽管能够,然而在组件外面获取比拟麻烦,所以咱们能够设置到路由记录的props字段上,间接注入为组件的props,这样应用就不便多了,批改router.js

// router.js// ...const createRoute = (routes) => {    // ...    return routes.map((item) => {        return {            ...item,            component: () => {                return import('./pages/' + item.component)            },            children: createRoute(item.children),            meta: {                code: item.code            },            props: {// ++                breadcrumbObj: {                    breadcrumb: item.breadcrumb,                    code: item.code                }             }        }    })}// ...

这样在组件里申明一个breadcrumbObj属性即可获取到面包屑数据,能够看到把code也一起传过来了,这是因为还要依据以后路由的code从用户接口获取的面包屑数据中取出该路由code对应的面包屑数据,而后把两局部的进行合并,这个工作为了防止让每个组件都要做一遍,咱们能够写在一个全局的mixin里,批改main.js

// ...Vue.mixin({    props: {        breadcrumbObj: {            type: Object,            default: () => null        }    },    computed: {        breadcrumb() {            if (!this.breadcrumbObj) {                return []            }            let {                code,                breadcrumb            } = this.breadcrumbObj            // 用户接口获取的面包屑数据            let breadcrumbData = this.$store.state.userInfo.breadcrumb            // 以后路由是否存在面包屑数据            let firstBreadcrumb = breadcrumbData && Array.isArray(breadcrumbData[code]) ? breadcrumbData[code] : []            // 合并两局部的面包屑数据            return firstBreadcrumb.concat(breadcrumb || [])        }    }})// ...initApp()

最初咱们在Hello.vue组件外面渲染一下面包屑:

<template>  <div class="container">    <el-breadcrumb separator="/">      <el-breadcrumb-item v-for="(item, index) in breadcrumb" :key="index">{{item}}</el-breadcrumb-item>    </el-breadcrumb>    // ...  </div></template>

当然,咱们的面包屑是不须要反对点击的,如果需要的话能够批改一下面包屑的数据结构。

接口申请

接口申请应用的是axios,然而会做一些根底配置、拦挡申请和响应,因为还是有一些场景须要间接应用未配置的axios,所以咱们默认创立一个新实例,先装置:

npm install axios

而后新建一个/src/api/目录,在外面新增一个httpInstance.js文件:

import axios from 'axios'// 创立一个新实例const http = axios.create({    timeout: 10000,// 超时工夫设为10秒    withCredentials: true,// 跨域申请时是否须要应用凭证,设置为须要    headers: {        'X-Requested-With': 'XMLHttpRequest'// 表明是ajax申请    },})export default http

而后减少一个申请拦截器:

// ...// 申请拦截器http.interceptors.request.use(function (config) {    // 在发送申请之前做些什么    return config;}, function (error) {    // 对申请谬误做些什么    return Promise.reject(error);});// ...

其实啥也没做,先写进去,留着不同的我的项目按需批改。

最初减少一个响应拦截器:

// ...import { Message } from 'element-ui'// ...// 响应拦截器http.interceptors.response.use(    function (response) {        // 对谬误进行对立解决        if (response.data.code !== '0') {            // 弹出谬误提醒            if (!response.config.noMsg && response.data.msg) {                Message.error(response.data.msg)            }            return Promise.reject(response)        } else if (response.data.code === '0' && response.config.successNotify && response.data.msg) {            // 弹出胜利提醒            Message.success(response.data.msg)        }        return Promise.resolve({            code: response.data.code,            msg: response.data.msg,            data: response.data.data,        })    },    function (error) {        // 登录过期        if (error.status === 403) {            location.reload()            return        }        // 超时提醒        if (error.message.indexOf('timeout') > -1) {            Message.error('申请超时,请重试!')        }        return Promise.reject(error)    },)// ...

咱们约定一个胜利的响应(状态码为200)构造如下:

{    code: '0',    msg: 'xxx',    data: xxx}

code不为0即便状态码为200也代表申请出错,那么弹出错误信息提示框,如果某次申请不心愿自动弹出提示框的话也能够禁止,只有在申请时加上配置参数noMsg: true即可,比方:

axios.get('/xxx', {    noMsg: true})

申请胜利默认不弹提醒,需要的话能够设置配置参数successNotify: true

状态码在非[200,300)之间的谬误只解决两种,登录过期和申请超时,其余状况可依据我的项目自行批改。

多语言

多语言应用vue-i18n实现,先装置:

npm install vue-i18n@8

vue-i18n9.x版本反对的是Vue3,所以咱们应用8.x版本。

而后创立一个目录/src/i18n/,在目录下新建index.js文件用来创立i18n实例:

import Vue from 'vue'import VueI18n from 'vue-i18n'Vue.use(VueI18n)const i18n = new VueI18n()export default i18n

除了创立实例其余啥也没做,别急,接下来咱们一步步来。

咱们的总体思路是,多语言的源数据在/src/i18n/下,而后编译成json文件放到我的项目的/public/i18n/目录下,页面的初始默认语言也是和用户信息接口一起返回,页面依据默认的语言类型应用ajax申请public目录下的对应json文件,调用VueI18n的办法动静进行设置。

这么做的目标首先是不便批改页面默认语言,其次是多语言文件不和我的项目代码打包到一起,缩小打包工夫,按需申请,缩小不必要的资源申请。

接下来咱们新建页面的中英文数据,目录构造如下:

比方中文的hello.json文件内容如下(疏忽笔者的低水平翻译~):

index.js文件里导入hello.json文件及ElementUI的语言文件,并合并导出:

import hello from './hello.json'import elementLocale from 'element-ui/lib/locale/lang/zh-CN'export default {    hello,    ...elementLocale}

为什么是...elementLocale呢,因为传给Vue-i18n的多语言数据结构是这样的:

咱们是把index.js的整个导出对象作为vue-i18n的多语言数据的,而ElementUI的多语言文件是这样的:

所以咱们须要把这个对象的属性和hello属性合并到一个对象上。

接下来咱们须要把它导出的数据到写到一个json文件里并输入到public目录下,这能够间接写个js脚本文件来做这个事件,然而为了和我的项目的源码离开咱们写成一个npm包。

创立一个npm工具包

咱们在我的项目的平级下创立一个包目录,并应用npm init初始化:

命名为-tool的起因是后续可能还会有相似编译多语言这种需要,所以取一个通用名字,不便前面减少其余性能。

命令行交互工具应用Commander.js,装置:

npm install commander

而后新建入口文件index.js

#!/usr/bin/env nodeconst {    program} = require('commander');// 编译多语言文件const buildI18n = () => {    console.log('编译多语言文件');}program    .command('i18n') // 增加i18n命令    .action(buildI18n)program.parse(process.argv);

因为咱们的包是要作为命令行工具应用的,所以文件第一行须要指定脚本的解释程序为node,而后应用commander配置了一个i18n命令,用来编译多语言文件,后续如果要增加其余性能新增命令即可,执行文件有了,咱们还要在包的package.json文件里增加一个bin字段,用来批示咱们的包里有可执行文件,让npm在安装包的时候顺便给咱们创立一个符号链接,把命令映射到文件。

// hello-tool/package.json{    "bin": {        "hello": "./index.js"    }}

因为咱们的包还没有公布到npm,所以间接链接到我的项目上应用,先在hello-tool目录下执行:

npm link

而后到咱们的hello world目录下执行:

npm link hello-tool

当初在命令行输出hello i18n试试:

编译多语言文件

接下来欠缺buildI18n函数的逻辑,次要分三步:

1.清空目标目录,也就是/public/i18n目录

2.获取/src/i18n下的各种多语言文件导出的数据

3.写入到json文件并输入到/public/i18n目录下

代码如下:

const path = require('path')const fs = require('fs')// 编译多语言文件const buildI18n = () => {    // 多语言源目录    let srcDir = path.join(process.cwd(), 'src/i18n')    // 目标目录    let destDir = path.join(process.cwd(), 'public/i18n')    // 1.清空目标目录,clearDir是一个自定义办法,递归遍历目录进行删除    clearDir(destDir)    // 2.获取源多语言导出数据    let data = {}    let langDirs = fs.readdirSync(srcDir)    langDirs.forEach((dir) => {        let dirPath = path.join(srcDir, dir)        // 读取/src/i18n/xxx/index.js文件,获取导出的多语言对象,存储到data对象上        let indexPath = path.join(dirPath, 'index.js')        if (fs.statSync(dirPath).isDirectory() && fs.existsSync(indexPath)) {            // 应用require加载该文件模块,获取导出的数据            data[dir] = require(indexPath)        }    })    // 3.写入到目标目录    Object.keys(data).forEach((lang) => {        // 创立public/i18n目录        if (!fs.existsSync(destDir)) {            fs.mkdirSync(destDir)        }        let dirPath = path.join(destDir, lang)        let filePath = path.join(dirPath, 'index.json')        // 创立多语言目录        if (!fs.existsSync(dirPath)) {            fs.mkdirSync(dirPath)        }        // 创立json文件        fs.writeFileSync(filePath, JSON.stringify(data[lang], null, 4))    })    console.log('多语言编译实现');}

代码很简略,接下来咱们运行命令:

报错了,提醒不能在模块外应用import,其实新版本的nodejs曾经反对ES6的模块语法了,能够把文件后缀换成.mjs,或者在package.json文件里减少type=module字段,然而都要做很多批改,这咋办呢,有没有更简略的办法呢?把多语言文件换成commonjs模块语法?也能够,然而不太优雅,不过好在babel提供了一个@babel/register包,能够把babel绑定到noderequire模块上,而后能够在运行时进行即时编译,也就是当require('/src/i18n/xxx/index.js')时会先由babel进行编译,编译完当然就不存在import语句了,先装置:

npm install @babel/core @babel/register @babel/preset-env

而后新建一个babel配置文件:

// hello-tool/babel.config.jsmodule.exports = {  'presets': ['@babel/preset-env']}

最初在hello-tool/index.js文件里应用:

const path = require('path')const {    program} = require('commander');const fs = require('fs')require("@babel/register")({    configFile: path.resolve(__dirname, './babel.config.js'),})// ...

接下来再次运行命令:

能够看到编译实现了,文件也输入到了public目录下,然而json文件里存在一个default属性,这一层显然咱们是不须要的,所以require('i18n/xxx/index.js')时咱们存储导出的default对象即可,批改hello-tool/index.js

const buildI18n = () => {    // ...    langDirs.forEach((dir) => {        let dirPath = path.join(srcDir, dir)        let indexPath = path.join(dirPath, 'index.js')        if (fs.statSync(dirPath).isDirectory() && fs.existsSync(indexPath)) {            data[dir] = require(indexPath).default// ++        }    })    // ...}

成果如下:

应用多语言文件

首先批改一下用户接口的返回数据,减少默认语言字段:

// /src/store.js// ...async getUserInfo(ctx) {    let userInfo = {        // ...        language: 'zh_CN'// 默认语言    }    ctx.commit('setUserInfo', userInfo)}// ...

而后在main.js外面获取完用户信息后立即申请并设置多语言:

// /src/main.jsimport { setLanguage } from './utils'// ++import i18n from './i18n'// ++const initApp = async () => {  await store.dispatch('getUserInfo')  await setLanguage(store.state.userInfo.language)// ++  new Vue({    i18n,// ++    router,    store,    render: h => h(App),  }).$mount('#app')}

setLanguage办法会申请多语言文件并切换:

// /src/utils/index.jsimport axios from 'axios'import i18n from '../i18n'// 申请并设置多语言数据const languageCache = {}export const setLanguage = async (language = 'zh_CN') => {    let languageData = null    // 有缓存,应用缓存数据    if (languageCache[language]) {        languageData = languageCache[language]    } else {        // 没有缓存,发动申请        const {            data        } = await axios.get(`/i18n/${language}/index.json`)        languageCache[language] = languageData = data    }    // 设置语言环境的 locale 信息    i18n.setLocaleMessage(language, languageData)    // 批改语言环境    i18n.locale = language}

而后把各个组件里显示的信息都换成$t('xxx')模式,当然,菜单和路由都须要做相应的批改,成果如下:

能够发现ElementUI组件的语言并没有变动,这是当然的,因为咱们还没有解决它,批改很简略,ElementUI反对自定义i18n的解决办法:

// /src/main.js// ...Vue.use(ElementUI, {  i18n: (key, value) => i18n.t(key, value)})// ...

通过CLI插件生成初始多语言文件

最初还有一个问题,就是我的项目初始化时还没有多语言文件怎么办,难道我的项目创立完还要先手动运行命令编译一下多语言?有几种解决办法:

1.最终个别会提供一个我的项目脚手架,所以默认的模板里咱们就能够间接加上初始的多语言文件;

2.启动服务和打包时先编译一下多语言文件,像这样:

"scripts": {    "serve": "hello i18n && vue-cli-service serve",    "build": "hello i18n && vue-cli-service build"  }

3.开发一个Vue CLI插件来帮咱们在我的项目创立完时主动运行一次多语言编译命令;

接下来简略实现一下第三种形式,同样在我的项目同级新建一个插件目录,并创立相应的文件(留神插件的命名标准):

依据插件开发标准,index.jsService插件的入口文件,Service插件能够批改webpack配置,创立新的 vue-cli service命令或者批改曾经存在的命令,咱们用不上,咱们的逻辑在generator.js里,这个文件会在两个场景被调用:

1.我的项目创立期间,CLI插件被作为我的项目创立preset的一部分被装置时

2.我的项目创立实现时通过vue addvue invoke独自装置插件时调用

咱们须要的刚好是在我的项目创立时或装置该插件时主动帮咱们运行多语言编译命令,generator.js须要导出一个函数,内容如下:

const {    exec} = require('child_process');module.exports = (api) => {    // 为了不便在我的项目里看到编译多语言的命令,咱们把hello i18n增加到我的项目的package.json文件里,批改package.json文件能够应用提供的api.extendPackage办法    api.extendPackage({        scripts: {            buildI18n: 'hello i18n'        }    })    // 该钩子会在文件写入硬盘后调用    api.afterInvoke(() => {        // 获取我的项目的残缺门路        let targetDir = api.generator.context        // 进入我的项目文件夹,而后运行命令        exec(`cd ${targetDir} && npm run buildI18n`, (error, stdout, stderr) => {            if (error) {                console.error(error);                return;            }            console.log(stdout);            console.error(stderr);        });    })}

咱们在afterInvoke钩子里运行编译命令,因为太早运行可能依赖都还没有装置实现,另外咱们还获取了我的项目的残缺门路,这是因为通过preset配置插件时,插件被调用时可能不在理论的我的项目文件夹,比方咱们在a文件夹下通过该命令创立b我的项目:

vue create b

插件被调用时是在a目录,显然hello-i18n包是被装置在b目录,所以咱们要先进入我的项目理论目录而后运行编译命令。

接下来测试一下,先在我的项目下装置该插件:

npm install --save-dev file:残缺门路\vue-cli-plugin-i18n

而后通过如下命令来调用插件的生成器:

vue invoke vue-cli-plugin-i18n

成果如下:

能够看到我的项目的package.json文件外面曾经注入了编译命令,并且命令也主动执行生成了多语言文件。

Mock数据

Mock数据举荐应用Mock,应用很简略,新建一个mock数据文件:

而后在/api/index.js里引入:

就这么简略,该申请即可被拦挡:

规范化

无关规范化的配置,比方代码格调查看、git提交标准等,笔者之前写过一篇组件库搭建的文章,其中一个大节具体的介绍了配置过程,可移步:【万字长文】从零配置一个vue组件库-规范化配置大节。

其余

申请代理

本地开发测试接口申请时难免会遇到跨域问题,能够配置一下webpack-dev-server的代理选项,新建vue.config.js文件:

module.exports = {    devServer: {        proxy: {            '^/api/': {                target: 'http://xxx:xxx',                changeOrigin: true            }        }    }}

编译node_modules内的依赖

默认状况下babel-loader会疏忽所有node_modules中的文件,然而有些依赖可能是没有通过编译的,比方咱们本人编写的一些包为了省事就不编译了,那么如果用了最新的语法,在低版本浏览器上可能就无奈运行了,所以打包的时候也须要对它们进行编译,要通过Babel显式转译一个依赖,能够在这个transpileDependencies选项配置,批改vue.config.js

module.exports = {    // ...    transpileDependencies: ['your-package-name']}

环境变量

须要环境变量能够在我的项目根目录下新建.env文件,须要留神的是如果要通过插件渲染.结尾的模板文件,要用_来代替点,也就是_env,最终会渲染为.结尾的文件。

脚手架

当咱们设计好了一套我的项目构造后,必定是作为模板来疾速创立我的项目的,个别会创立一个脚手架工具来生成,然而Vue CLI提供了preset(预设)的能力,所谓preset指的是一个蕴含创立新我的项目所需预约义选项和插件的 JSON对象,所以咱们能够创立一个CLI插件来创立模板,而后创立一个preset,再把这个插件配置到preset里,这样应用vue create命令创立我的项目时应用咱们的自定义preset即可。

创立一个生成模板的CLI插件

新建插件目录如下:

能够看到这次咱们创立了一个generator目录,因为咱们须要渲染模板,而模板文件就会放在这个目录下,新建一个template目录,而后把咱们前文配置的我的项目构造残缺的复制进去(不包含package.json):

当初咱们来实现/generator/index.js文件的内容:

1.因为不包含package.json,所以咱们要批改vue我的项目默认的package.json,增加咱们须要的货色,应用的就是后面提到的api.extendPackage办法:

// generator/index.jsmodule.exports = (api) => {    // 扩大package.json    api.extendPackage({        "dependencies": {            "axios": "^0.25.0",            "element-ui": "^2.15.6",            "vue-i18n": "^8.27.0",            "vue-router": "^3.5.3",            "vuex": "^3.6.2"        },        "devDependencies": {            "mockjs": "^1.1.0",            "sass": "^1.49.7",            "sass-loader": "^8.0.2",            "hello-tool": "^1.0.0"// 留神这里,不要遗记把咱们的工具包加上        }    })}

增加了一些额定的依赖,包含咱们后面开发的hello-tool

2.渲染模板

module.exports = (api) => {    // ...    api.render('./template')}

render办法会渲染template目录下的所有文件。

创立一个自定义preset

插件都有了,最初让咱们来创立一下自定义preset,新建一个preset.json文件,把咱们后面写的template插件和i18n插件一起配置进去:

{    "plugins": {        "vue-cli-plugin-template": {            "version": "^1.0.0"        },        "vue-cli-plugin-i18n": {            "version": "^1.0.0"        }    }}

同时为了测试这个preset,咱们再创立一个空目录:

而后进入test-preset目录运行vue create命令时指定咱们的preset门路即可:

vue create --preset ../preset.json my-project

成果如下:

近程应用preset

preset本地测试没问题了就能够上传到仓库里,之后就能够给他人应用了,比方笔者上传到了这个仓库:https://github.com/wanglin2/Vue_project_design,那么你能够这么应用:

vue create --preset wanglin2/Vue_project_design project-name

总结

如果有哪里不对的或是更好的,评论区见~