在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.jsexport default [ { path: '/company', name: 'company', component: () => import(/* webpackChunkName: "company" */ './index.vue'), meta: { title: '公司简介' } }]
公共页面对立入口
// views/index.jsexport { default as companyRoute } from './Company/index.js'export { default as ruleRoute } from './Rule/index.js'
定制我的项目中的公共页面
// config/proA.jsimport { 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.jsimport 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.jsimport 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.jsimport 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 lernanpm 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装置依赖packageAlerna add packageA --scope=proA // 给proA装置依赖packageAyarn 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的反对