-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
wip: vapor mode #2509
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
zhiyuanzmj
wants to merge
17
commits into
vuejs:main
Choose a base branch
from
zhiyuanzmj:vapor
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
wip: vapor mode #2509
Changes from 7 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
d5f389c
wip: vapor mode
zhiyuanzmj 534fb5e
chore: Merge remote-tracking branch 'zmj/main' into vapor
zhiyuanzmj 4b4d889
chore: update vue
zhiyuanzmj c8933a6
chore: update peerDependencies for pkg.pr.new
zhiyuanzmj 01a808a
chore: revert deps
zhiyuanzmj 668f808
chore: move createTemplateRefSetter to top setup
zhiyuanzmj 02a3c5b
chore: remove vapor flag
zhiyuanzmj af099d5
chore: Merge branch 'main' into vapor
zhiyuanzmj def481c
chore: apply edison's suggestions
zhiyuanzmj 451f1da
chore: fix lint
zhiyuanzmj d0540d6
chore: update deps
zhiyuanzmj 0bfd480
chore: update name
zhiyuanzmj d0e0ad3
chore: Merge branch 'main' into vapor
zhiyuanzmj 2d3ac95
chore: fix lint
zhiyuanzmj 7f65849
chore: return instance
zhiyuanzmj 4636368
feat: co-usage with KeepAlive
zhiyuanzmj ef302bd
chore: update
zhiyuanzmj File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| import { routerKey } from './injectionSymbols' | ||
| import { | ||
| _RouterLinkI, | ||
| getLinkClass, | ||
| type RouterLinkProps, | ||
| useLink, | ||
| } from './RouterLink' | ||
| import { RouteLocationRaw } from './typed-routes' | ||
| import { | ||
| computed, | ||
| createComponentWithFallback, | ||
| createDynamicComponent, | ||
| defineVaporComponent, | ||
| inject, | ||
| PropType, | ||
| reactive, | ||
| } from 'vue' | ||
|
|
||
| export const VaporRouterLinkImpl = /*#__PURE__*/ defineVaporComponent({ | ||
| name: 'RouterLink', | ||
| // @ts-ignore | ||
| compatConfig: { MODE: 3 }, | ||
| props: { | ||
| to: { | ||
| type: [String, Object] as PropType<RouteLocationRaw>, | ||
| required: true, | ||
| }, | ||
| replace: Boolean, | ||
| activeClass: String, | ||
| // inactiveClass: String, | ||
| exactActiveClass: String, | ||
| custom: Boolean, | ||
| ariaCurrentValue: { | ||
| type: String as PropType<RouterLinkProps['ariaCurrentValue']>, | ||
| default: 'page', | ||
| }, | ||
| viewTransition: Boolean, | ||
| }, | ||
|
|
||
| useLink, | ||
|
|
||
| setup(props, { slots, attrs }) { | ||
| const link = reactive(useLink(props)) | ||
| const { options } = inject(routerKey)! | ||
|
|
||
| const elClass = computed(() => ({ | ||
| [getLinkClass( | ||
| props.activeClass, | ||
| options.linkActiveClass, | ||
| 'router-link-active' | ||
| )]: link.isActive, | ||
| // [getLinkClass( | ||
| // props.inactiveClass, | ||
| // options.linkInactiveClass, | ||
| // 'router-link-inactive' | ||
| // )]: !link.isExactActive, | ||
| [getLinkClass( | ||
| props.exactActiveClass, | ||
| options.linkExactActiveClass, | ||
| 'router-link-exact-active' | ||
| )]: link.isExactActive, | ||
| })) | ||
|
|
||
| return createDynamicComponent(() => { | ||
zhiyuanzmj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const children = slots.default && slots.default(link) | ||
| return props.custom | ||
| ? () => children | ||
| : () => | ||
| createComponentWithFallback( | ||
| 'a', | ||
| { | ||
| 'aria-current': () => | ||
| link.isExactActive ? props.ariaCurrentValue : null, | ||
| href: () => link.href, | ||
| // this would override user added attrs but Vue will still add | ||
| // the listener, so we end up triggering both | ||
| onClick: () => link.navigate, | ||
| class: () => elClass.value, | ||
| $: [() => attrs], | ||
| }, | ||
| { | ||
| default: () => children, | ||
| } | ||
| ) | ||
| }) | ||
| }, | ||
| }) | ||
|
|
||
| export const VaporRouterLink: _RouterLinkI = VaporRouterLinkImpl as any | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| import { | ||
| inject, | ||
| provide, | ||
| PropType, | ||
| ref, | ||
| unref, | ||
| ComponentPublicInstance, | ||
| VNodeProps, | ||
| computed, | ||
| AllowedComponentProps, | ||
| ComponentCustomProps, | ||
| watch, | ||
| VNode, | ||
| createTemplateRefSetter, | ||
| createComponent, | ||
| createDynamicComponent, | ||
| defineVaporComponent, | ||
| type VaporComponent, | ||
| type VaporSlot, | ||
| } from 'vue' | ||
| import type { RouteLocationNormalizedLoaded } from './typed-routes' | ||
| import type { RouteLocationMatched } from './types' | ||
| import { | ||
| matchedRouteKey, | ||
| viewDepthKey, | ||
| routerViewLocationKey, | ||
| } from './injectionSymbols' | ||
| import { assign } from './utils' | ||
| import { isSameRouteRecord } from './location' | ||
| import type { RouterViewProps, RouterViewDevtoolsContext } from './RouterView' | ||
|
|
||
| export type { RouterViewProps, RouterViewDevtoolsContext } | ||
|
|
||
| export const VaporRouterViewImpl = /*#__PURE__*/ defineVaporComponent({ | ||
| name: 'RouterView', | ||
| // #674 we manually inherit them | ||
| inheritAttrs: false, | ||
| props: { | ||
| name: { | ||
| type: String as PropType<string>, | ||
| default: 'default', | ||
| }, | ||
| route: Object as PropType<RouteLocationNormalizedLoaded>, | ||
| }, | ||
|
|
||
| // Better compat for @vue/compat users | ||
| // https://github.com/vuejs/router/issues/1315 | ||
| // @ts-ignore | ||
| compatConfig: { MODE: 3 }, | ||
zhiyuanzmj marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| setup(props, { attrs, slots }) { | ||
| const injectedRoute = inject(routerViewLocationKey)! | ||
| const routeToDisplay = computed<RouteLocationNormalizedLoaded>( | ||
| () => props.route || injectedRoute.value | ||
| ) | ||
| const injectedDepth = inject(viewDepthKey, 0) | ||
| // The depth changes based on empty components option, which allows passthrough routes e.g. routes with children | ||
| // that are used to reuse the `path` property | ||
| const depth = computed<number>(() => { | ||
| let initialDepth = unref(injectedDepth) | ||
| const { matched } = routeToDisplay.value | ||
| let matchedRoute: RouteLocationMatched | undefined | ||
| while ( | ||
| (matchedRoute = matched[initialDepth]) && | ||
| !matchedRoute.components | ||
| ) { | ||
| initialDepth++ | ||
| } | ||
| return initialDepth | ||
| }) | ||
| const matchedRouteRef = computed<RouteLocationMatched | undefined>( | ||
| () => routeToDisplay.value.matched[depth.value] | ||
| ) | ||
|
|
||
| provide( | ||
| viewDepthKey, | ||
| computed(() => depth.value + 1) | ||
| ) | ||
| provide(matchedRouteKey, matchedRouteRef) | ||
| provide(routerViewLocationKey, routeToDisplay) | ||
|
|
||
| const viewRef = ref<ComponentPublicInstance>() | ||
|
|
||
| // watch at the same time the component instance, the route record we are | ||
| // rendering, and the name | ||
| watch( | ||
| () => [viewRef.value, matchedRouteRef.value, props.name] as const, | ||
| ([instance, to, name], [oldInstance, from]) => { | ||
| // copy reused instances | ||
| if (to) { | ||
| // this will update the instance for new instances as well as reused | ||
| // instances when navigating to a new route | ||
| to.instances[name] = instance | ||
| // the component instance is reused for a different route or name, so | ||
| // we copy any saved update or leave guards. With async setup, the | ||
| // mounting component will mount before the matchedRoute changes, | ||
| // making instance === oldInstance, so we check if guards have been | ||
| // added before. This works because we remove guards when | ||
| // unmounting/deactivating components | ||
| if (from && from !== to && instance && instance === oldInstance) { | ||
| if (!to.leaveGuards.size) { | ||
| to.leaveGuards = from.leaveGuards | ||
| } | ||
| if (!to.updateGuards.size) { | ||
| to.updateGuards = from.updateGuards | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // trigger beforeRouteEnter next callbacks | ||
| if ( | ||
| instance && | ||
| to && | ||
| // if there is no instance but to and from are the same this might be | ||
| // the first visit | ||
| (!from || !isSameRouteRecord(to, from) || !oldInstance) | ||
| ) { | ||
| ;(to.enterCallbacks[name] || []).forEach(callback => | ||
| callback(instance) | ||
| ) | ||
| } | ||
| }, | ||
| { flush: 'post' } | ||
| ) | ||
|
|
||
| const ViewComponent = computed(() => { | ||
| const matchedRoute = matchedRouteRef.value | ||
| return matchedRoute && matchedRoute.components![props.name] | ||
| }) | ||
|
|
||
| // props from route configuration | ||
| const routeProps = computed(() => { | ||
| const route = routeToDisplay.value | ||
| const currentName = props.name | ||
| const matchedRoute = matchedRouteRef.value | ||
| const routePropsOption = matchedRoute && matchedRoute.props[currentName] | ||
| return routePropsOption | ||
| ? routePropsOption === true | ||
| ? route.params | ||
| : typeof routePropsOption === 'function' | ||
| ? routePropsOption(route) | ||
| : routePropsOption | ||
| : null | ||
| }) | ||
|
|
||
| const setRef = createTemplateRefSetter() | ||
|
|
||
| return createDynamicComponent(() => { | ||
zhiyuanzmj marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (!ViewComponent.value) { | ||
| return () => | ||
| normalizeSlot(slots.default, { | ||
| Component: ViewComponent.value, | ||
| route: routeToDisplay.value, | ||
| }) | ||
| } | ||
|
|
||
| return () => { | ||
| const component = createComponent( | ||
| ViewComponent.value as VaporComponent, | ||
| { | ||
| $: [() => assign({}, routeProps.value, attrs)], | ||
| } | ||
| ) | ||
| setRef(component, viewRef) | ||
|
|
||
| return ( | ||
| normalizeSlot(slots.default, { | ||
| Component: component, | ||
| route: routeToDisplay.value, | ||
| }) || component | ||
| ) | ||
| } | ||
| }) | ||
| }, | ||
| }) | ||
|
|
||
| function normalizeSlot(slot: VaporSlot | undefined, data: any) { | ||
| if (!slot) return null | ||
| return slot(data) | ||
| } | ||
|
|
||
| // export the public type for h/tsx inference | ||
| // also to avoid inline import() in generated d.ts files | ||
| /** | ||
| * Component to display the current route the user is at. | ||
| */ | ||
| export const VaporRouterView = VaporRouterViewImpl as unknown as { | ||
| new (): { | ||
| $props: AllowedComponentProps & | ||
| ComponentCustomProps & | ||
| VNodeProps & | ||
| RouterViewProps | ||
|
|
||
| $slots: { | ||
| default?: ({ | ||
| Component, | ||
| route, | ||
| }: { | ||
| Component: VNode | ||
| route: RouteLocationNormalizedLoaded | ||
| }) => VNode[] | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.