美文网首页
Vue-cli & lerna多项目管理

Vue-cli & lerna多项目管理

作者: 说叁两事 | 来源:发表于2021-03-30 15:26 被阅读0次

    在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 仓库

    参考文档

    相关文章

      网友评论

          本文标题:Vue-cli & lerna多项目管理

          本文链接:https://www.haomeiwen.com/subject/vklghltx.html