关于javascript:基于Vue2x的前端架构我们是这么做的

29次阅读

共计 16347 个字符,预计需要花费 41 分钟才能阅读完成。

通过 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.js
export 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.js

export 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) ? breadcrumbData : []
            // 合并两局部的面包屑数据
            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 node

const {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.js
module.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.js
import {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.js
import 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.js

module.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

总结

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

正文完
 0