乐趣区

关于mono:Vuecli-lerna多项目管理

在 Vue-cli 3.X 环境下,基于同一类型的流动,能够多个页面复用,大部分组件能够专用的背景

Multiple 解决形式

  • 每一个流动创立一个分支,在不同的分支上各自保护
  • 如果须要保护复用代码时,任选某一分支进行批改,通过 git cherry-pick <commit id> 进行平行迁徙。

Monorepo 解决形式

仅在同一分支下进行多我的项目的保护,各个功能模块解构,通过我的项目配置项进行个性化配置。

目录构造

应用 vue-cli 初始化我的项目后,须要进行目录的调整,将 src/ 下的目录构造晋升到顶层构造中,并进行从新整合。

|- views
  |- index.js // 通用页面的对立入口
  |- Company
    |- index.vue // 通用页面 Company 构造、款式、逻辑
    |- index.js  // Company 页面路由
  |- Rule
    |- index.vue
    |- index.js
|- components
|- core
  |- instance  // 和 app 实例挂钩的办法
  |- libs  // 和 app 实例无关的办法
|- assets
  |- images
  |- fonts
|- store
  |- index.js  // 通用状态
  |- types.js  // 事件类型
|- config
  |- proA.js  // 我的项目资源配置
  |- proB.js
|- projects  // 我的项目定制资源
  |- proA
  |- proB

不同我的项目的区别齐全在于 config/ 文件的配置和 projects/ 下的我的项目定义;同级其余目录是各个我的项目通用的内容。

以上目录构造需在 我的项目工作目录的顶层构造下 ,不容许包裹,为后续和lerna 联合做筹备。

这里须要调整 vue.config.js 中的入口文件 pages: entries 的门路地址。

提取公共页面 & 路由

公共页面示例:

// views/Company/index.vue
<template>
 ...
</template>
<script>
...
</script>
<style scoped>
...
</style>

公共页面路由

// views/Company/index.js
export default [
  {
    path: '/company',
    name: 'company',
    component: () => import(/* webpackChunkName: "company" */ './index.vue'),
    meta: {title: '公司简介'}
  }
]

公共页面对立入口

// views/index.js
export {default as companyRoute} from './Company/index.js'
export {default as ruleRoute} from './Rule/index.js'

定制我的项目中的公共页面

// config/proA.js
import {
  companyRoute,
  ruleRoute
} from '../views/index.js'
...
export const logoUrl = '' // 还能够定制其它的内容

export const routes = [
  ...companyRoute,
  ...ruleRoute
]

我的项目中应用公共页面

projects/proA 为例:

目录构造

|- assets
|- components
|- mixins
|- router
|- store
|- pages
|- App.vue
|- main.js

我的项目主路由

// projects/proA/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import {routes} from '../../config/proA'
import Home from '../pages/Home'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      redirect: '/home'
    },
    {
      path: '/home',
      name: 'Home',
      component: Home,
      meta: {title: ''}
    },
    ...routes
  ]
})

其中:Home/index.vue 是定制化的。

状态治理

多我的项目是独立运行时,状态提取不会相互烦扰,若一次性运行多个我的项目,通用状态会被批改。

通用状态提取

// store/index.js
import types from './types'

export const initialState = {userInfo: {},
  ...
}
export function getGetters (store) {
  return {userId: () => store.userInfo.userID,
    ...
  }
}
export function getMutations (store) {
  return {[types.INITIALMUTATIONTYPES.USER_INFO] (val) {store.userInfo = val},
    ...
  }
}

config/proA.js 文件中追加:

...
export * from '../store/index.js'
export * from '../store/types.js'
...

我的项目中应用

小型我的项目,应用 vue.observable 治理状态

定义我的项目的主状态治理

// projects/proA/store/index.js

import vue from 'vue'
import {initialState, getGetters, getMutations} from '../../../config/proA'

export const store = vue.observable({
  ...initialState,
  customState: '', // 我的项目公有状态
  ...
})
store._getters = {...getGetters(store),
  customGetter() {  // 我的项目公有
      return store.customState
  },
  ...
}
store._mutations = {...getMutation(store),
  ...  // 我的项目公有
}
export const mutation = {...getMutations(store),
  ...  // 我的项目公有
}

定义辅助办法mapGetters

拷贝 vuex 局部代码到 core/libs/helpers.js 文件中

export const mapGetters = (getters) => {const res = {}
  if (!isValidMap(getters)) {console.error('[vuex] mapGetters: mapper parameter must be either an Array or an Object')
  }
  normalizeMap(getters).forEach(({key, val}) => {res[key] = function mappedGetter () {if (!(val in this.$store._getters)) {console.error(`[vuex] unknown getter: ${val}`)
        return
      }
      return this.$store._getters[val]()}
  })
  return res
}
export function normalizeMap (map) {if (!isValidMap(map)) {return []
  }
  return Array.isArray(map)
    ? map.map(key => ({ key, val: key}))
    : Object.keys(map).map(key => ({ key, val: map[key] }))
}
export function isValidMap (map) {return Array.isArray(map) || isObject(map)
}
export function isObject (obj) {return obj !== null && typeof obj === 'object'}

core/libs/index.js 中追加:

export * from './helpers'

*.vue中应用

// projects/proA/pages/Home/index.vue
<script>
...
import {mapGetters} from '../../../core/libs/'

export default {data () {
    return {...}
  },
  computed: {
    ...mapGetters(['userId']),
    ...
  }
...
</script>

组件治理

组件对立入口

借助 webpackrequire.context办法将 /components/ 下的组件整合

const ret = {}
const requireComponent = require.context(
  './',  // 指定递归的文件目录
  true,  // 是否递归文件子目录
  /[A-Z]\w+\.(vue)$/  // 落地文件
)
requireComponent.keys().forEach(fileName => {const componentConfig = requireComponent(fileName)
  const component = componentConfig.default || componentConfig
  const componentName = component.name || fileName.split('/').pop().replace(/\.\w+$/, '')
  // ret[componentName] = () => requireComponent(fileName)
  ret[componentName] = component
})
export default ret

定义布局配置

// config/proA.js 追加
...
export const layouts = {
  Home: [
    {
      componentType: 'CompA',
      request: {fetch () {const res = []
          return res
        }
      },
      response: {filter (res) {return []
        },
        effect () {}
      }
    },
    {componentType: 'CompB'},
    {componentType: 'CompC'},
    {componentType: 'CompD'}
  ]
}

我的项目中应用

proA/Home/index.vue

<template>
...
  <template v-for="componentConfig of layouts">
    <component
      v-bind="dataValue"
      :is="componentConfig.componentType"
      :key="componentConfig.componentType"
    >
    </component>
  </template>
...
</template>
<script>
...
import {
  CompA,
  CompB
} from '../../components/'
import {layouts} from '../../config/proA'
...
export default {
  ...
  data () {
    return {
      ...
      layouts: layouts.Home,
      ...
    }
  },
  ...
  components: {
    CompA,
    CompB
  },
  ...
}
</script>

引入 lerna 治理我的项目

初始化 lerna 环境

npm i -g lerna
npm i -g yarn // 须要借助 yarn 的 workspaces 个性
cd <workplace>
lerna init
        // ^ >lerna init
        // lerna notice cli v4.0.0
        // lerna info Updating package.json
        // lerna info Creating lerna.json
        // lerna info Creating packages directory
        // lerna success Initialized Lerna files

lerna初始化环境会做以下几件事:

  • 更新 package.json 文件
  • 创立 lerna.json 文件
  • 创立 packages/ 目录

指定工作区域

  • 批改 package.json 文件
  "private": true,  // private 须要为 true
  "workspaces": [
    "projects/*",
    "components/*"
  ],
  • 批改 lerna.json 文件
{
  "npmClient": "yarn",
  "useWorkspaces": true,  // 共用 package.json 文件的 workspaces 配置
  "version": "independent"  // 每个我的项目独立治理版本号
}

创立新我的项目

lerna create @demo/cli
// ^ lerna success create New package @demo/cli created at .projects/cli

lerna会做以下几件事:

  • 命令行是否指定指标工作区
  • 若无指定工作区,选 lerna.json 配置项 packages 第一项工作区为指标工作区
  • 通过交互命令行界面创立我的项目目录

    • 新我的项目目录构造

      |- projects
        |- cli
          |- package.json
          |- README.md
          |- lib
            |- cli.js
          |- __tests__

这里讲诉一下,为什么整体的我的项目目录不容许包裹,如不容许应用 src 包裹我的项目目录:lerna指定工作区 loc 限度为顶级目录构造

lerna create @demo/cli2 'components'
// ^ lerna success create New package @demo/cli2 created at .components/cli2

将已有我的项目改装为 lerna 的工作我的项目

在我的项目目录下追加 package.json 文件即可

{
  "name": "proA",
  "version": "0.0.0"
}

查看我的项目列表

// 以 yarn 命令查看
yarn workspaces info 
// 以 lerna 命令查看
lerna list

我的项目目录下有 package.json 形容的能力被检索进去。

治理依赖

yarn workspace proA add packageA // 给 proA 装置依赖 packageA
lerna add packageA --scope=proA // 给 proA 装置依赖 packageA
yarn workspace proA add package-a@0.0.0 // 将 packageA 作为 proA 的依赖进行装置
larna add package-a --scope=proA  // 将 packageA 作为 proA 的依赖进行装置
//  ^ == yarn workspace 装置本地包,第一次必须加上 lerna.json 中的版本号(后续肯定不要再加版本号),否则,会从 npm.org 近程检索装置
yarn add -W -D typescript // 在 root 下装置专用依赖 typescript

通过以上几步,能够将我的项目的依赖独自治理

// projects/proA/package.json
{
  "name": "proA",
  "version": "0.0.0",
  "dependencies": {"packageA": "^1.0.0"}
}
// ./package.json 根目录文件
{
  "name": "monoDemo",
  "version": "0.0.0",
  "private": true,
  "workspaces": [
    "projects/*",
    "components/*",
  ],
  "dependencies": ["typescript": "^0.4.9"]
}

查看改变的我的项目

lerna changed

提交批改

  • 初始化提交:创立我的项目或切新分支第一次提交

    git add .
    git commit -m <message>
    git push --set-upstream origin <branch>
  • 后续提交

    git add .
    git commit -m <message>
    lerna version  --conventional-commits [-m <message>]
    // ^ 应用 --conventional-commits 参数 lerna 会依据 semer 版本规定主动生成版本号,否则,通过交互式命令行手动抉择版本号。

能够提前预置 lerna version 的提交日志

{
  "command": {
    "version": {"message": "chore(release): publish %s"
    }
  }
}

lerna version会做一下几件事:

  • 找出从上一个版本公布以来有过变更的 package
  • 提醒开发者确定要公布的版本号
  • 将所有更新过的的 package 中的 package.json 的 version 字段更新
  • 将依赖更新过的 package 的 包中的依赖版本号更新
  • 更新 lerna.json 中的 version 字段
  • 提交上述批改,并打一个 tag
  • 推送到 git 仓库

参考文档

  • 应用 MonoRepo 治理前端我的项目
  • husky 对 monoRepo 的反对
退出移动版