关键词
2-1 脚手架发布功能和流程讲解
2-2 绘制项目发布架构设计图
3-1 git flow 基础流程讲解
3-2 git flow 多人协作流程讲解(详细讲解大厂git flow流程) 点击查看【processon】
3-3 脚手架git flow prepare阶段架构设计
ProcessOn画图
3-4 脚手架git flow 执行阶段架构设计 -Init
ProcessOn画图
4-1 云构建+云发布整体流程设计 4-2 云构建+云发布详细流程设计1 4-3 云构建+云发布详细流程设计2 4-4 深入讲解云发布原理 点击查看【processon】
5-1 创建publish模块
本模块在调试的时候出现问题:
查找原因为:
最后在一个空目录中输入以下命令进行调试: cloudscope-cli publish --targetPath /Users/liumingzhou/Documents/imoocCourse/Web前端架构师/cloudscope-cli/commands/publish 打印出:publish
5-2 publish基本流程开发
接下来的重点就是编写业务代码:cloudscope-cli/commands/publish/lib/index.js
'use strict';
const Command = require('@cloudscope-cli/command')
const log = require('@cloudscope-cli/log')
class PublishCommand extends Command {
init(){
// 处理参数
console.log('init',this._argv)
}
async exec(){
try {
const startTime = new Date().getTime()
//1. 初始化检查
this.prepare()
//2.Git Flow自动化
//3.云构建 + 云发布
const endTime = new Date().getTime()
log.info('本次发布耗时',Math.floor(endTime-startTime)/1000+'秒')
} catch (e) {
log.error(e.message)
if(process.env.LOG_LEVEL === 'verbose'){
log.error(e.message)
}
}
}
prepare(){
}
}
function init(argv){
return new PublishCommand(argv)
}
module.exports = init
module.exports.PublishCommand = PublishCommand;
5-3 项目发布前预检查流程开发
结合上一节代码,本节主要内容为:
prepare(){
// 1.确认项目是否为npm项目
const projectPath = process.cwd()
const pkgPath = path.resolve(projectPath,'package.json')
log.verbose('package.json',pkgPath)
if(!fs.existsSync(pkgPath)){
throw new Error('package.json不存在')
}
//2. 确认是否包含name\version\build命令
const pkg = fse.readJsonSync(pkgPath)
const {name,version,scripts} = pkg
if(!name || !version || !scripts || !scripts.build ){
throw new Error('package.json信息不全,请检查是否存在name、version和scripts(需提供build命令)')
}
this.projectInfo = {name,version,dir:projectPath}
}
本章内容测试代码上传至:https://github.com/liugezhou/vue-router-demo
6-1 vue-router-next完整运行流程解析
vue-router-next源码解析 vue-router常见问题:
通过imooc-cli脚手架安装一个vue3标准模版
// src/router.js
const {createWebHistory,createRouter} from 'vue-router',
import Home from './pages/Home'
import My from './pages/My'
import Order from './pages/Order'
const routes = [
path:'/',name:'root',redirect:'/home'
},{
path:'/home',name:'home',component:Home
},{
path:'/my',name:'my',component:My
},{
path:'/order',name:'order',component:Order
}]
const routerHitory = createWebHistory()
const router = createRouter({
history:routerHitory,
routes
})
export default router;
// App.vue
<template>
<div id="vue3">vue3 template</div>
<router-link to='/home'>Home | </router-link>
<router-link to='/order'>Order | </router-link>
<router-link to='/my'>My | </router-link>
<router-view />
</template>
<script>
export default {
name: 'Vue3',
}
</script>
<style>
#vue3 {
width: 100%;
height: 100%;
}
</style>
6-2 vue-router路由模式+history路由部署详细教学
Vue-router路由模式
hash和history模式的区别
server {
listen 8081;
server_name resource;
root /Users/liumingzhou/XXXXX/dist;
autoindex on;
location / {
//跨域设置
add_header Access-Control-Allow-Origin *;
try_files: $uri $uri/ /index.html;
}
// 缓存设置
add_header Cache-Control "no-cache, must-revalidate";
}
6-3 vue-cli源码调试+dev模式history fallback原理讲解
为什么Vue的dev模式下不需要配置history fallback?
6-4 vue-router初始化过程源码分析
我们并没有定义router-link和router-view,为什么代码里能直接使用?
function createWebHistory(base) {
// 传入的base进行处理
base = normalizeBase(base);
//historyNavigation提供了一些方法:location/push/replace/state
// 该方法的实现浏览器URL变化但页面不刷新(push),核心是使用了浏览器对象模型history.pushState()和history.replaceState()方法。
const historyNavigation = useHistoryStateNavigation(base);
//生成一个listener:destory和listen方法
const historyListeners = useHistoryListeners(base, historyNavigation.state, historyNavigation.location, historyNavigation.replace);
function go(delta, triggerListeners = true) {
if (!triggerListeners)
historyListeners.pauseListeners();
history.go(delta);
}
//将上面的事件拼装到一起,生成一个routerHistory对象返回
const routerHistory = assign({
// it's overridden right after
location: '',
base,
go,
createHref: createHref.bind(null, base),
}, historyNavigation, historyListeners);
Object.defineProperty(routerHistory, 'location', {
enumerable: true,
get: () => historyNavigation.location.value,
});
Object.defineProperty(routerHistory, 'state', {
enumerable: true,
get: () => historyNavigation.state.value,
});
return routerHistory;
}
返回routerHistory对象后,接着进入到createRouter方法中,源码以及注释如下:
// 从调用createRouter处,options中传入的参数为:history和routes
function createRouter(options) {
// 第一步生成matcher,matcher的作用是实现路由匹配
// createRouterMatcher会为每一个简单或复杂的路由生成一个正则表达式
const matcher = createRouterMatcher(options.routes, options);
let parseQuery$1 = options.parseQuery || parseQuery;
let stringifyQuery$1 = options.stringifyQuery || stringifyQuery;
// 拿到history对象,是createWebHistory或者为createWebHashHistory
let routerHistory = options.history;
if ((process.env.NODE_ENV !== 'production') && !routerHistory)
throw new Error('Provide the "history" option when calling "createRouter()":' +
' https://next.router.vuejs.org/api/#history.');
//一些路由守卫的初始化、useCallbacks方法返回一个闭包。
//每一个路由守卫都对应了一个闭包(代码就不贴了,主要返回了三个方法:add,list,reset,主要作用是缓存路由守卫)。
const beforeGuards = useCallbacks();
const beforeResolveGuards = useCallbacks();
const afterGuards = useCallbacks();
// 生成默认router
const currentRoute = shallowRef(START_LOCATION_NORMALIZED);
………………
// 一些初始化操作
………………
// 这里的router即为最终的router对象,包含一系列的属性和方法
const router = {
currentRoute,
addRoute,
removeRoute,
hasRoute,
getRoutes,
resolve,
options,
push,
replace,
go,
back: () => go(-1),
forward: () => go(1),
beforeEach: beforeGuards.add,
beforeResolve: beforeResolveGuards.add,
afterEach: afterGuards.add,
onError: errorHandlers.add,
isReady,
//此处的install方法是在执行app.user(router)的时候会执行到这里(即当这个router被返回到main.js后,下一步就会执行app.user(router),然后就会进入到这方法中去)
install(app) {
const router = this;
//在此处注册了组件RouterLink和RouterView
app.component('RouterLink', RouterLink);
app.component('RouterView', RouterView);
//全局主注册了$router $route
app.config.globalProperties.$router = router;
Object.defineProperty(app.config.globalProperties, '$route', {
enumerable: true,
get: () => unref(currentRoute),
});
if (isBrowser &&!started &&
currentRoute.value === START_LOCATION_NORMALIZED) {
started = true;
//浏览器中push后,就会进行页面的渲染
push(routerHistory.location).catch(err => {
if ((process.env.NODE_ENV !== 'production'))
warn('Unexpected error when starting the router:', err);
});
}
const reactiveRoute = {};
for (let key in START_LOCATION_NORMALIZED) {
reactiveRoute[key] = computed(() => currentRoute.value[key]);
}
// 使用app.provide来做组件的传递
// router-view和router-link中的参数是通过这里传递下去的
// 关于provide的用法,见本节内容往下
app.provide(routerKey, router);
app.provide(routeLocationKey, reactive(reactiveRoute));
app.provide(routerViewLocationKey, currentRoute);
let unmountApp = app.unmount;
installedApps.add(app);
app.unmount = function () {
installedApps.delete(app);
if (installedApps.size < 1) {
removeHistoryListener();
currentRoute.value = START_LOCATION_NORMALIZED;
started = false;
ready = false;
}
unmountApp();
};
},
};
return router;
6-5 vue3高级特性:vue插件+provide跨组件通信
浏览器中如何实现URL变化但页面不刷新
<button @click="jump">Jump</button>
………………
<script>
import { useRouter } from 'vue-router'
export default {
name: 'App',
setup(){
const router = useRouter();
return{
jump(){
// eslint-disable-next-line no-debugger
debugger
router.push('/order')
}
}
}
}
</script>
然后step into到router.push方法中,由此开始调试,进入pushWithRedirect()方法中(如下图)
然后一步一步的,调试源码到最后,最终会通过history.pushState()方法,来改变地址而不发生页面的更新。
在上图的高亮部分resolve(to)是路由匹配的相关实现,下节继续。
6-7 vue-router路由匹配源码分析
我们输入路由后如何与我们自己定义的 routes中的路由进行匹配,就涉及到vue-router的核心概念 matcher。 两个关键点是:createRouter以及上一节提到的resollve方法。
本节重点讲解这个resolve方法,我们假定从 /home跳转到/order,代码以及注释如下:
function resolve(rawLocation, currentLocation) {
// 第一步是拿到currentLocation,即当前路由相关信息 【/home相关的】
currentLocation = assign({}, currentLocation || currentRoute.value);
// 判断传进来的路由‘/order’参数是不是string
if (typeof rawLocation === 'string') {
//进行一个形式的格式化吧
let locationNormalized = parseURL(parseQuery$1, rawLocation, currentLocation.path);
//最关键的一步是调用matcher下的resolve方法,传入两个参数 ‘/order’和‘/home’,到这里我们需要继续step into到这个方法中去调试。关键代码为: matchers.find(m => m.re.test(path));
let matchedRoute = matcher.resolve({ path: locationNormalized.path }, currentLocation);
let href = routerHistory.createHref(locationNormalized.fullPath);
if ((process.env.NODE_ENV !== 'production')) {
if (href.startsWith('//'))
warn(`Location "${rawLocation}" resolved to "${href}". A resolved location cannot start with multiple slashes.`);
else if (!matchedRoute.matched.length) {
warn(`No match found for location with path "${rawLocation}"`);
}
}
// locationNormalized is always a new object
return assign(locationNormalized, matchedRoute, {
params: decodeParams(matchedRoute.params),
hash: decode(locationNormalized.hash),
redirectedFrom: undefined,
href,
});
}
…………………………………………
}
6-8 vue3新特性defineComponent讲解1 && 6-9 vue3新特性defineComponent讲解2
router-view如何实现组件动态渲染(涉及Vue动态组件)
import { defineComponent,h } from 'vue'
// import Home from '../pages/Home';
const TestComponent2 = defineComponent({
name:'TestComponent2',
props:{},
setup(props, {slots} ){
return ()=> {
return h('div',{
class:'test-component2',
onClick(){
alert('click')
}
},slots.default())
}
// return () =>{
// return h(Home,{
// onClick(){
// alert('You Clicked the Home Component!')
// }
// })
// }
}
})
export default TestComponent2
6-10 深入解析router-view源码
const RouterViewImpl = /*#__PURE__*/ defineComponent({
name: 'RouterView',
inheritAttrs: false,
props: {
name: {
type: String,
default: 'default',
},
route: Object,
},
// setup在整个组件初始化的时候只会执行一遍,但下面的render function,也就是40行的return部分会执行多次
setup(props, { attrs, slots }) {
(process.env.NODE_ENV !== 'production') && warnDeprecatedUsage();
// injectedRoute决定router-view的刷新
const injectedRoute = inject(routerViewLocationKey);
// injectedRoute.value
const routeToDisplay = computed(() => props.route || injectedRoute.value);
const depth = inject(viewDepthKey, 0);
const matchedRouteRef = computed(() => routeToDisplay.value.matched[depth]);
provide(viewDepthKey, depth + 1);
provide(matchedRouteKey, matchedRouteRef);
provide(routerViewLocationKey, routeToDisplay);
// 空的ref用来装载马上要渲染的view-router实例
const viewRef = ref();
watch(() => [viewRef.value, matchedRouteRef.value, props.name], ([instance, to, name], [oldInstance, from, oldName]) => {
if (to) {
to.instances[name] = instance;
if (from && from !== to && instance && instance === oldInstance) {
if (!to.leaveGuards.size) {
to.leaveGuards = from.leaveGuards;
}
if (!to.updateGuards.size) {
to.updateGuards = from.updateGuards;
}
}
}
if (instance &&
to &&
(!from || !isSameRouteRecord(to, from) || !oldInstance)) {
(to.enterCallbacks[name] || []).forEach(callback => callback(instance));
}
// 默认为pre属性,post在页面渲染之后执行 watch 监听
}, { flush: 'post' });
return () => {
const route = routeToDisplay.value;
const matchedRoute = matchedRouteRef.value;
const ViewComponent = matchedRoute && matchedRoute.components[props.name];
const currentName = props.name;
if (!ViewComponent) {
return normalizeSlot(slots.default, { Component: ViewComponent, route });
}
const routePropsOption = matchedRoute.props[props.name];
const routeProps = routePropsOption
? routePropsOption === true
? route.params
: typeof routePropsOption === 'function'
? routePropsOption(route)
: routePropsOption
: null;
const onVnodeUnmounted = vnode => {
// remove the instance reference to prevent leak
if (vnode.component.isUnmounted) {
matchedRoute.instances[currentName] = null;
}
};
const component = h(ViewComponent, assign({}, routeProps, attrs, {
onVnodeUnmounted,
ref: viewRef,
}));
return (
normalizeSlot(slots.default, { Component: component, route }) ||
component);
};
},
});