在 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>
组件治理
组件对立入口
借助
webpack
的require.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 的反对