From cf655d0450d3757657f3855c66d371e74dac4b3a Mon Sep 17 00:00:00 2001 From: Patrick Housley Date: Wed, 5 Jul 2017 18:28:42 -0500 Subject: [PATCH 1/9] Added loadChildren to RouteConfig interface. --- types/router.d.ts | 2 ++ types/test/index.ts | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/types/router.d.ts b/types/router.d.ts index 4d4c6a1d1..c7d9fe2c6 100644 --- a/types/router.d.ts +++ b/types/router.d.ts @@ -12,6 +12,7 @@ export type NavigationGuard = ( from: Route, next: (to?: RawLocation | false | ((vm: Vue) => any) | void) => void ) => any +export type LoadChildrenPromise = () => Promise; declare class VueRouter { constructor (options?: RouterOptions); @@ -83,6 +84,7 @@ export interface RouteConfig { props?: boolean | Object | RoutePropsFunction; caseSensitive?: boolean; pathToRegexpOptions?: PathToRegexpOptions; + loadChildren?: string | LoadChildrenPromise; } export interface RouteRecord { diff --git a/types/test/index.ts b/types/test/index.ts index 5fc8a51fc..4dee16454 100644 --- a/types/test/index.ts +++ b/types/test/index.ts @@ -82,6 +82,24 @@ const router = new VueRouter({ to.params; return "/child"; } + }, + { + path: "asyncChildren-webpackLoader", + loadChildren: "./children/routes#routes" + }, + { + path: "asyncChildren", + loadChildren: () => new Promise(resolve => { + resolve([ + { + path: "childA", + components: { + default: Foo, + bar: Bar + } + } + ]); + }) } ]}, { path: "/home", alias: "/" }, @@ -161,7 +179,7 @@ router.go(-1); router.back(); router.forward(); -const Components: ComponentOptions | typeof Vue = router.getMatchedComponents(); +const Components: (ComponentOptions | typeof Vue)[] = router.getMatchedComponents(); const vm = new Vue({ router, From 506af0d495262c713f95568998c8a05b79a69cba Mon Sep 17 00:00:00 2001 From: Patrick Housley Date: Wed, 5 Jul 2017 22:47:50 -0500 Subject: [PATCH 2/9] Updated Route Map Creation - Updated to throw an error when loadChildren is provided but is not a function. - Updated to create a regex that will match an async route and any children of that route. --- src/create-route-map.js | 20 +++++++++-- test/unit/specs/create-map.spec.js | 54 +++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/src/create-route-map.js b/src/create-route-map.js index 4077392a1..ee26f5476 100644 --- a/src/create-route-map.js +++ b/src/create-route-map.js @@ -48,6 +48,8 @@ function addRouteRecord ( matchAs?: string ) { const { path, name } = route + const hasAsyncChildren = typeof route.loadChildren === 'function'; + if (process.env.NODE_ENV !== 'production') { assert(path != null, `"path" is required in a route configuration.`) assert( @@ -55,13 +57,22 @@ function addRouteRecord ( `route config "component" for path: ${String(path || name)} cannot be a ` + `string id. Use an actual component instead.` ) + + if (route.loadChildren) { + assert( + hasAsyncChildren, + `route config "loadChildren" for path: ${String(path || name)} cannot be a ` + + `${typeof route.loadChildren}. Use a method that returns a Promise with your child routes.` + ) + } } const pathToRegexpOptions: PathToRegexpOptions = route.pathToRegexpOptions || {} const normalizedPath = normalizePath( path, parent, - pathToRegexpOptions.strict + pathToRegexpOptions.strict, + hasAsyncChildren ) if (typeof route.caseSensitive === 'boolean') { @@ -86,6 +97,10 @@ function addRouteRecord ( : { default: route.props } } + if (hasAsyncChildren) { + record.loadChildren = route.loadChildren; + } + if (route.children) { // Warn if route is named, does not redirect and has a default child route. // If users navigate to this route by name, the default child will @@ -161,8 +176,9 @@ function compileRouteRegex (path: string, pathToRegexpOptions: PathToRegexpOptio return regex } -function normalizePath (path: string, parent?: RouteRecord, strict?: boolean): string { +function normalizePath (path: string, parent?: RouteRecord, strict?: boolean, async?: boolean): string { if (!strict) path = path.replace(/\/$/, '') + if (async) path = `${path}(\/{0,1}.*)`; if (path[0] === '/') return path if (parent == null) return path return cleanPath(`${parent.path}/${path}`) diff --git a/test/unit/specs/create-map.spec.js b/test/unit/specs/create-map.spec.js index 9d8e62f36..0fddaeddd 100644 --- a/test/unit/specs/create-map.spec.js +++ b/test/unit/specs/create-map.spec.js @@ -71,7 +71,7 @@ describe('Creating Route Map', function () { expect(console.warn.calls.argsFor(0)[0]).toMatch('vue-router] Named Route \'bar\'') }) - it('in production, it has not logged this warning', function () { + it('in production, has not logged a warning concerning named route of parent and default subroute', function () { maps = createRouteMap(routes) expect(console.warn).not.toHaveBeenCalled() }) @@ -164,4 +164,56 @@ describe('Creating Route Map', function () { expect(pathList).toEqual(['/foo/', '/bar']) }) }) + + describe('async children', function () { + it('should log a warning in development when loadChildren is not a method', function () { + const maps = function () { + return createRouteMap([ + { + name: 'asyncFoo', + path: '/asyncFoo', + loadChildren: '/async/routes' + } + ]) + } + + process.env.NODE_ENV = 'development' + expect(maps).toThrow() + }) + + it('should not log a warning in production when loadChildren is not a method', function () { + const maps = function () { + return createRouteMap([ + { + name: 'asyncFoo', + path: '/asyncFoo', + loadChildren: '/async/routes' + } + ]) + } + + expect(maps).not.toThrow() + }) + + it('should create a regex that matches the route and any sub-routes of the async route', function () { + const { nameMap } = createRouteMap([ + { + name: 'asyncFoo', + path: '/asyncFoo', + loadChildren: () => new Promise(function (resolve) { + return [ + { + name: 'asyncBar', + component: Foo + } + ] + }) + } + ]) + + expect(nameMap.asyncFoo.regex.test('/asyncFoo')).toBe(true) + expect(nameMap.asyncFoo.regex.test('/asyncFoo/')).toBe(true) + expect(nameMap.asyncFoo.regex.test('/asyncFoo/asyncBar')).toBe(true) + }) + }) }) From 4b8651e1c61fa1b75e2bc13dad48a859a17cbeff Mon Sep 17 00:00:00 2001 From: Patrick Housley Date: Wed, 5 Jul 2017 23:14:32 -0500 Subject: [PATCH 3/9] Added tests to ensure create matcher is functioning with async routes. --- src/create-route-map.js | 9 ++++---- test/unit/specs/create-map.spec.js | 8 +++---- test/unit/specs/create-matcher.spec.js | 32 ++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/create-route-map.js b/src/create-route-map.js index ee26f5476..a1bd469e7 100644 --- a/src/create-route-map.js +++ b/src/create-route-map.js @@ -81,7 +81,7 @@ function addRouteRecord ( const record: RouteRecord = { path: normalizedPath, - regex: compileRouteRegex(normalizedPath, pathToRegexpOptions), + regex: compileRouteRegex(normalizedPath, pathToRegexpOptions, hasAsyncChildren), components: route.components || { default: route.component }, instances: {}, name, @@ -164,8 +164,8 @@ function addRouteRecord ( } } -function compileRouteRegex (path: string, pathToRegexpOptions: PathToRegexpOptions): RouteRegExp { - const regex = Regexp(path, [], pathToRegexpOptions) +function compileRouteRegex (path: string, pathToRegexpOptions: PathToRegexpOptions, async?: boolean): RouteRegExp { + const regex = Regexp(`${path}${async ? '(\/{0,1}.*)' : ''}`, [], pathToRegexpOptions) if (process.env.NODE_ENV !== 'production') { const keys: any = {} regex.keys.forEach(key => { @@ -177,8 +177,7 @@ function compileRouteRegex (path: string, pathToRegexpOptions: PathToRegexpOptio } function normalizePath (path: string, parent?: RouteRecord, strict?: boolean, async?: boolean): string { - if (!strict) path = path.replace(/\/$/, '') - if (async) path = `${path}(\/{0,1}.*)`; + if (!strict || async) path = path.replace(/\/$/, '') if (path[0] === '/') return path if (parent == null) return path return cleanPath(`${parent.path}/${path}`) diff --git a/test/unit/specs/create-map.spec.js b/test/unit/specs/create-map.spec.js index 0fddaeddd..1d84dc444 100644 --- a/test/unit/specs/create-map.spec.js +++ b/test/unit/specs/create-map.spec.js @@ -200,14 +200,14 @@ describe('Creating Route Map', function () { { name: 'asyncFoo', path: '/asyncFoo', - loadChildren: () => new Promise(function (resolve) { - return [ + loadChildren: function () { + return Promise.resolve([ { name: 'asyncBar', component: Foo } - ] - }) + ]) + } } ]) diff --git a/test/unit/specs/create-matcher.spec.js b/test/unit/specs/create-matcher.spec.js index bb932c702..b404a7fdd 100644 --- a/test/unit/specs/create-matcher.spec.js +++ b/test/unit/specs/create-matcher.spec.js @@ -4,6 +4,18 @@ import { createMatcher } from '../../../src/create-matcher' const routes = [ { path: '/', name: 'home', component: { name: 'home' }}, { path: '/foo', name: 'foo', component: { name: 'foo' }}, + { + path: '/async', + name: 'async', + loadChildren: function () { + return Promise.resolve([ + { + name: 'asyncBar', + component: Foo + } + ]) + } + } ] describe('Creating Matcher', function () { @@ -32,4 +44,24 @@ describe('Creating Matcher', function () { match({ name: 'foo' }, routes[0]) expect(console.warn).not.toHaveBeenCalled() }) + + describe('async children', function () { + it('should match the async route', function () { + const { name, matched } = match({ path: '/async' }, routes[0]) + expect(matched.length).toBe(1) + expect(name).toBe('async') + }) + + it('should match the async route ending with a slash', function () { + const { name, matched } = match('/async/' , routes[0]) + expect(matched.length).toBe(1) + expect(name).toBe('async') + }) + + it('should match the async route when the children have not been loaded', function () { + const { name, matched } = match('/async/foo', routes[0]) + expect(matched.length).toBe(1) + expect(name).toBe('async') + }) + }) }) From f57ecb11f7599378150cf82eaf0fbe2449f00a39 Mon Sep 17 00:00:00 2001 From: Patrick Housley Date: Wed, 5 Jul 2017 23:57:42 -0500 Subject: [PATCH 4/9] Updated createRoute to carry forward the loadChildren property. --- src/util/route.js | 1 + test/unit/specs/create-matcher.spec.js | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/util/route.js b/src/util/route.js index 9fee9c099..e09ec7d18 100644 --- a/src/util/route.js +++ b/src/util/route.js @@ -20,6 +20,7 @@ export function createRoute ( query: location.query || {}, params: location.params || {}, fullPath: getFullPath(location, stringifyQuery), + loadChildren: record && record.loadChildren ? record.loadChildren : null, matched: record ? formatMatch(record) : [] } if (redirectedFrom) { diff --git a/test/unit/specs/create-matcher.spec.js b/test/unit/specs/create-matcher.spec.js index b404a7fdd..857a1d4e4 100644 --- a/test/unit/specs/create-matcher.spec.js +++ b/test/unit/specs/create-matcher.spec.js @@ -63,5 +63,15 @@ describe('Creating Matcher', function () { expect(matched.length).toBe(1) expect(name).toBe('async') }) + + it('should container property loadChildren with a value of null for non-async routes', function () { + const { loadChildren } = match('/foo', routes[0]) + expect(loadChildren).toBe(null) + }) + + it('should container property loadChildren without a value of null for async routes', function () { + const { loadChildren } = match('/async/foo', routes[0]) + expect(loadChildren).not.toBe(null) + }) }) }) From a86e85cd275ab15aecabaabf8b049f97b4521a4a Mon Sep 17 00:00:00 2001 From: Patrick Housley Date: Thu, 6 Jul 2017 00:33:10 -0500 Subject: [PATCH 5/9] Added support for adding routes as children of an existing route. --- src/create-matcher.js | 8 +++++-- src/create-route-map.js | 5 ++-- src/index.js | 4 ++-- test/unit/specs/api.spec.js | 48 ++++++++++++++++++++++++++++++++++++- 4 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/create-matcher.js b/src/create-matcher.js index 4e96d215e..fdcd70b33 100644 --- a/src/create-matcher.js +++ b/src/create-matcher.js @@ -19,8 +19,12 @@ export function createMatcher ( ): Matcher { const { pathList, pathMap, nameMap } = createRouteMap(routes) - function addRoutes (routes) { - createRouteMap(routes, pathList, pathMap, nameMap) + function addRoutes (routes, parent) { + let parentRoute: RouteRecord; + if (pathMap && parent) { + parentRoute = pathMap[parent]; + } + createRouteMap(routes, pathList, pathMap, nameMap, parentRoute) } function match ( diff --git a/src/create-route-map.js b/src/create-route-map.js index a1bd469e7..8031fecbf 100644 --- a/src/create-route-map.js +++ b/src/create-route-map.js @@ -8,7 +8,8 @@ export function createRouteMap ( routes: Array, oldPathList?: Array, oldPathMap?: Dictionary, - oldNameMap?: Dictionary + oldNameMap?: Dictionary, + parentRoute?: RouteRecord ): { pathList: Array; pathMap: Dictionary; @@ -20,7 +21,7 @@ export function createRouteMap ( const nameMap: Dictionary = oldNameMap || Object.create(null) routes.forEach(route => { - addRouteRecord(pathList, pathMap, nameMap, route) + addRouteRecord(pathList, pathMap, nameMap, route, parentRoute) }) // ensure wildcard routes are always at the end diff --git a/src/index.js b/src/index.js index a1a7b8507..12285a4fc 100644 --- a/src/index.js +++ b/src/index.js @@ -206,8 +206,8 @@ export default class VueRouter { } } - addRoutes (routes: Array) { - this.matcher.addRoutes(routes) + addRoutes (routes: Array, parent?: string) { + this.matcher.addRoutes(routes, parent) if (this.history.current !== START) { this.history.transitionTo(this.history.getCurrentLocation()) } diff --git a/test/unit/specs/api.spec.js b/test/unit/specs/api.spec.js index 57e912cd9..7d42c590f 100644 --- a/test/unit/specs/api.spec.js +++ b/test/unit/specs/api.spec.js @@ -75,6 +75,39 @@ describe('router.addRoutes', () => { expect(components.length).toBe(1) expect(components[0].name).toBe('A') }) + + it('should load children to an existing parent route', function () { + const router = new Router({ + mode: 'abstract', + routes: [ + { path: '/a', component: { name: 'A' }} + ] + }) + + router.push('/a') + let components = router.getMatchedComponents() + expect(components.length).toBe(1) + expect(components[0].name).toBe('A') + + router.push('/a/b') + components = router.getMatchedComponents() + expect(components.length).toBe(0) + + /** + * A given route represents a hierarchy of components loaded to the DOM + * where each parent must contain a `router-view` for it's children. + */ + router.addRoutes([{ path: 'b', component: { name: 'B' }}], '/a') + components = router.getMatchedComponents() + expect(components.length).toBe(2) + expect(components[1].name).toBe('B') + + // make sure it preserves previous routes + router.push('/a') + components = router.getMatchedComponents() + expect(components.length).toBe(1) + expect(components[0].name).toBe('A') + }) }) describe('router.push/replace callbacks', () => { @@ -98,7 +131,20 @@ describe('router.push/replace callbacks', () => { router = new Router({ routes: [ - { path: '/foo', component: Foo } + { path: '/foo', component: Foo }, + { + path: '/asyncFoo', + name: 'asyncFoo', + loadChildren: function () { + return Promise.resolve([ + { + path: 'asyncBar', + name: 'asyncBar', + component: Foo + } + ]) + } + } ] }) From 7b871d15331994f9ccc6c571f2fa53f02eab90cc Mon Sep 17 00:00:00 2001 From: Patrick Housley Date: Thu, 6 Jul 2017 02:39:28 -0500 Subject: [PATCH 6/9] Completed feature for loading of async children. --- src/create-matcher.js | 47 ++++++++++++++++++++++--- src/create-route-map.js | 8 +++-- src/history/base.js | 33 ++++++++++++++---- test/unit/specs/api.spec.js | 56 +++++++++++++++++++++++++++++- test/unit/specs/create-map.spec.js | 1 + 5 files changed, 130 insertions(+), 15 deletions(-) diff --git a/src/create-matcher.js b/src/create-matcher.js index fdcd70b33..ead0acba6 100644 --- a/src/create-matcher.js +++ b/src/create-matcher.js @@ -10,7 +10,8 @@ import { normalizeLocation } from './util/location' export type Matcher = { match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route; - addRoutes: (routes: Array) => void; + addRoutes: (routes: Array, parent?: string) => void; + loadAsyncChildren: (location: Route) => Promise<{}>; }; export function createMatcher ( @@ -20,9 +21,9 @@ export function createMatcher ( const { pathList, pathMap, nameMap } = createRouteMap(routes) function addRoutes (routes, parent) { - let parentRoute: RouteRecord; + let parentRoute: RouteRecord if (pathMap && parent) { - parentRoute = pathMap[parent]; + parentRoute = pathMap[parent] } createRouteMap(routes, pathList, pathMap, nameMap, parentRoute) } @@ -75,6 +76,43 @@ export function createMatcher ( return _createRoute(null, location) } + function loadAsyncChildren( + location: Route + ): Promise<{}> { + const asyncMatches = location.matched.filter(match => match && !!match.loadChildren) + if (!asyncMatches) { + return Promise.reject(new Error('No matched routes have async children.')) + } + + return Promise.all([ + ...asyncMatches.map(match => match.loadChildren()) + ]) + .then((children) => { + for (let i = 0; i < children.length; i++) { + const {name, path} = asyncMatches[i] + const parentConfig = pathMap[path].routeConfig + + // Remove the loadChildren property + if (name) { + delete nameMap[name] + } + + delete pathMap[path] + + // Load the children + addRoutes([ + Object.assign( + parentConfig, + { + children: children[i], + loadChildren: null + } + ) + ]) + } + }) + } + function redirect ( record: RouteRecord, location: Location @@ -172,7 +210,8 @@ export function createMatcher ( return { match, - addRoutes + addRoutes, + loadAsyncChildren } } diff --git a/src/create-route-map.js b/src/create-route-map.js index 8031fecbf..df0fe7f8e 100644 --- a/src/create-route-map.js +++ b/src/create-route-map.js @@ -49,7 +49,7 @@ function addRouteRecord ( matchAs?: string ) { const { path, name } = route - const hasAsyncChildren = typeof route.loadChildren === 'function'; + const hasAsyncChildren = typeof route.loadChildren === 'function' if (process.env.NODE_ENV !== 'production') { assert(path != null, `"path" is required in a route configuration.`) @@ -95,11 +95,12 @@ function addRouteRecord ( ? {} : route.components ? route.props - : { default: route.props } + : { default: route.props }, + routeConfig: route } if (hasAsyncChildren) { - record.loadChildren = route.loadChildren; + record.loadChildren = route.loadChildren } if (route.children) { @@ -134,6 +135,7 @@ function addRouteRecord ( aliases.forEach(alias => { const aliasRoute = { path: alias, + loadChildren: route.loadChildren, children: route.children } addRouteRecord( diff --git a/src/history/base.js b/src/history/base.js index 0c90dafa7..ccb27c496 100644 --- a/src/history/base.js +++ b/src/history/base.js @@ -61,17 +61,36 @@ export class History { this.errorCbs.push(errorCb) } - transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) { + transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function, childrenLoaded?: boolean) { const route = this.router.match(location, this.current) this.confirmTransition(route, () => { this.updateRoute(route) - onComplete && onComplete(route) - this.ensureURL() - // fire ready cbs once - if (!this.ready) { - this.ready = true - this.readyCbs.forEach(cb => { cb(route) }) + if (route.loadChildren && !childrenLoaded) { + this.router.matcher.loadAsyncChildren(route) + .then(() => { + // Perform transition again to ensure the proper component hierarchy is loaded + this.transitionTo(location, onComplete, onAbort, true) + }) + .catch((err) => { + if (onAbort) { + onAbort(err) + } + + if (err && !this.ready) { + this.ready = true + this.readyErrorCbs.forEach(cb => { cb(err) }) + } + }) + } else { + onComplete && onComplete(route) + this.ensureURL() + + // fire ready cbs once + if (!this.ready) { + this.ready = true + this.readyCbs.forEach(cb => { cb(route) }) + } } }, err => { if (onAbort) { diff --git a/test/unit/specs/api.spec.js b/test/unit/specs/api.spec.js index 7d42c590f..44f2f6c78 100644 --- a/test/unit/specs/api.spec.js +++ b/test/unit/specs/api.spec.js @@ -124,6 +124,16 @@ describe('router.push/replace callbacks', () => { } } + const Bar = { + beforeRouteEnter (to, from, next) { + calls.push(5) + setTimeout(() => { + calls.push(6) + next() + }, 1) + } + } + beforeEach(() => { calls = [] spy1 = jasmine.createSpy('complete') @@ -135,12 +145,13 @@ describe('router.push/replace callbacks', () => { { path: '/asyncFoo', name: 'asyncFoo', + component: Foo, loadChildren: function () { return Promise.resolve([ { path: 'asyncBar', name: 'asyncBar', - component: Foo + component: Bar } ]) } @@ -190,4 +201,47 @@ describe('router.push/replace callbacks', () => { done() }) }) + + describe('async children', function () { + it('push complete', done => { + router.push('/asyncFoo/asyncBar', () => { + expect(calls).toEqual([1, 2, 3, 4, 1, 2, 3, 4, 5, 6]) + done() + }) + }) + + it('push abort', done => { + router.push('/foo', spy1, spy2) + router.push('/asyncFoo/asyncBar', () => { + expect(calls).toEqual([1, 1, 2, 2, 3, 4, 1, 2, 3, 4, 5, 6]) + expect(spy1).not.toHaveBeenCalled() + expect(spy2).toHaveBeenCalled() + done() + }) + }) + + it('replace complete', done => { + router.replace('/asyncFoo/asyncBar', () => { + expect(calls).toEqual([1, 2, 3, 4, 1, 2, 3, 4, 5, 6]) + + let components = router.getMatchedComponents() + expect(components.length).toBe(2) + done() + }) + }) + + it('replace abort', done => { + router.replace('/foo', spy1, spy2) + router.replace('/asyncFoo/asyncBar', () => { + expect(calls).toEqual([1, 1, 2, 2, 3, 4, 1, 2, 3, 4, 5, 6]) + expect(spy1).not.toHaveBeenCalled() + expect(spy2).toHaveBeenCalled() + + let components = router.getMatchedComponents() + expect(components.length).toBe(2) + + done() + }) + }) + }) }) diff --git a/test/unit/specs/create-map.spec.js b/test/unit/specs/create-map.spec.js index 1d84dc444..544a62f28 100644 --- a/test/unit/specs/create-map.spec.js +++ b/test/unit/specs/create-map.spec.js @@ -204,6 +204,7 @@ describe('Creating Route Map', function () { return Promise.resolve([ { name: 'asyncBar', + path: '/asyncBar', component: Foo } ]) From 70caf455baf5fcc63402082c42b00491e2fecf60 Mon Sep 17 00:00:00 2001 From: Patrick Housley Date: Thu, 6 Jul 2017 16:22:29 -0500 Subject: [PATCH 7/9] Correcting flow types. --- flow/declarations.js | 5 +++++ src/create-matcher.js | 34 +++++++++++++++++++++------------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/flow/declarations.js b/flow/declarations.js index 0efebfa96..abfe7a5de 100644 --- a/flow/declarations.js +++ b/flow/declarations.js @@ -27,6 +27,8 @@ declare type NavigationGuard = ( declare type AfterNavigationHook = (to: Route, from: Route) => any +declare type LoadChildrenPromise = () => Promise; + type Position = { x: number, y: number }; declare type RouterOptions = { @@ -59,6 +61,7 @@ declare type RouteConfig = { props?: boolean | Object | Function; caseSensitive?: boolean; pathToRegexpOptions?: PathToRegexpOptions; + loadChildren: string | LoadChildrenPromise; } declare type RouteRecord = { @@ -73,6 +76,8 @@ declare type RouteRecord = { beforeEnter: ?NavigationGuard; meta: any; props: boolean | Object | Function | Dictionary; + loadChildren?: string | LoadChildrenPromise; + routeConfig?: RouteConfig; } declare type Location = { diff --git a/src/create-matcher.js b/src/create-matcher.js index ead0acba6..481acfed4 100644 --- a/src/create-matcher.js +++ b/src/create-matcher.js @@ -11,7 +11,7 @@ import { normalizeLocation } from './util/location' export type Matcher = { match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route; addRoutes: (routes: Array, parent?: string) => void; - loadAsyncChildren: (location: Route) => Promise<{}>; + loadAsyncChildren: (location: Route) => Promise; }; export function createMatcher ( @@ -78,19 +78,28 @@ export function createMatcher ( function loadAsyncChildren( location: Route - ): Promise<{}> { + ): Promise { const asyncMatches = location.matched.filter(match => match && !!match.loadChildren) if (!asyncMatches) { return Promise.reject(new Error('No matched routes have async children.')) } return Promise.all([ - ...asyncMatches.map(match => match.loadChildren()) + ...asyncMatches.map(match => + typeof match.loadChildren === 'function' + ? match.loadChildren() + : Promise.resolve([]) + ) ]) - .then((children) => { + .then(children => { for (let i = 0; i < children.length; i++) { const {name, path} = asyncMatches[i] const parentConfig = pathMap[path].routeConfig + const childRoutes: RouteConfig[] = children[1] + + if (!parentConfig) { + continue + } // Remove the loadChildren property if (name) { @@ -98,17 +107,16 @@ export function createMatcher ( } delete pathMap[path] + delete parentConfig.loadChildren + + if (parentConfig.children) { + parentConfig.children.push(...childRoutes) + } else { + parentConfig.children = childRoutes + } // Load the children - addRoutes([ - Object.assign( - parentConfig, - { - children: children[i], - loadChildren: null - } - ) - ]) + addRoutes([parentConfig]) } }) } From 12f54ea4064e70db7d8720f6cbe8c9699f5de082 Mon Sep 17 00:00:00 2001 From: Patrick Housley Date: Sat, 8 Jul 2017 01:04:25 -0500 Subject: [PATCH 8/9] Final cleanup and correction of issues. --- examples/async-children/app.js | 55 +++++++++++ examples/async-children/basic-async.js | 12 +++ examples/async-children/deep-async-a.js | 22 +++++ examples/async-children/deep-async-b.js | 21 ++++ .../async-children/deep-async-default-a.js | 17 ++++ .../async-children/deep-async-default-b.js | 16 ++++ examples/async-children/index.html | 6 ++ flow/declarations.js | 6 +- src/create-matcher.js | 57 ++++------- src/create-route-map.js | 23 ++++- src/history/base.js | 11 +-- src/index.js | 4 +- src/util/async-children.js | 84 ++++++++++++++++ test/e2e/specs/async-children.js | 70 ++++++++++++++ test/unit/specs/api.spec.js | 96 +++++++++++++++---- 15 files changed, 430 insertions(+), 70 deletions(-) create mode 100644 examples/async-children/app.js create mode 100644 examples/async-children/basic-async.js create mode 100644 examples/async-children/deep-async-a.js create mode 100644 examples/async-children/deep-async-b.js create mode 100644 examples/async-children/deep-async-default-a.js create mode 100644 examples/async-children/deep-async-default-b.js create mode 100644 examples/async-children/index.html create mode 100644 src/util/async-children.js create mode 100644 test/e2e/specs/async-children.js diff --git a/examples/async-children/app.js b/examples/async-children/app.js new file mode 100644 index 000000000..1aed83b17 --- /dev/null +++ b/examples/async-children/app.js @@ -0,0 +1,55 @@ +import Vue from 'vue' +import VueRouter from 'vue-router' + +Vue.use(VueRouter); + +const Home = { template: '
home
' } +const Foo = { template: '
foo
' } +const Bar = { template: '
bar
' } + +const routes = [ + { path: '/', component: Home }, + { path: '/foo', component: Foo }, + { path: '/bar', component: Bar }, + { + path: '/basic', + component: { template: '
' }, + loadChildren: () => import('./basic-async').then(asyncConfig => asyncConfig.routes) + }, + { + path: '/deep', + component: { template: '
' }, + loadChildren: () => import('./deep-async-a').then(asyncConfig => asyncConfig.routes) + }, + { + path: '/default-deep', + component: { template: '
' }, + loadChildren: () => import('./deep-async-default-a').then(asyncConfig => asyncConfig.routes) + } +] + +const router = new VueRouter({ + mode: 'history', + base: __dirname, + routes +}); + +new Vue({ + router, + template: ` +
+

Basic

+
    +
  • /
  • +
  • /foo
  • +
  • /bar
  • +
  • /basic
  • +
  • /basic/foo
  • +
  • /basic/bar
  • +
  • /deep/a/b
  • +
  • /default-deep
  • +
+ +
+ ` +}).$mount('#app') \ No newline at end of file diff --git a/examples/async-children/basic-async.js b/examples/async-children/basic-async.js new file mode 100644 index 000000000..9fcb2a3b6 --- /dev/null +++ b/examples/async-children/basic-async.js @@ -0,0 +1,12 @@ +import Vue from 'vue' +import VueRouter from 'vue-router' + +const BasicDefault = { template: '
basic-default
' } +const BasicFoo = { template: '
basic-foo
' } +const BasicBar = { template: '
basic-bar
' } + +export const routes = [ + { path: '', component: BasicDefault }, + { path: 'foo', component: BasicFoo }, + { path: 'bar', component: BasicBar } +] diff --git a/examples/async-children/deep-async-a.js b/examples/async-children/deep-async-a.js new file mode 100644 index 000000000..72d43d8da --- /dev/null +++ b/examples/async-children/deep-async-a.js @@ -0,0 +1,22 @@ +import Vue from 'vue' +import VueRouter from 'vue-router' + +const DeepA = { template: '
' } + +export const routes = [ + { + path: '', + component: {}, + loadChildren: () => Promise.reject('Default async route loaded when it should not have been.') + }, + { + path: 'test', + component: {}, + loadChildren: () => Promise.reject('Test async route loaded when it should not have been.') + }, + { + path: 'a', + component: DeepA, + loadChildren: () => import('./deep-async-b').then(asyncConfig => asyncConfig.routes) + } +] diff --git a/examples/async-children/deep-async-b.js b/examples/async-children/deep-async-b.js new file mode 100644 index 000000000..edb1fa003 --- /dev/null +++ b/examples/async-children/deep-async-b.js @@ -0,0 +1,21 @@ +import Vue from 'vue' +import VueRouter from 'vue-router' + +const DeepB = { template: '
deep-async-b
' } + +export const routes = [ + { + path: '', + component: {}, + loadChildren: () => Promise.reject('Default async route loaded when it should not have been.') + }, + { + path: 'test', + component: {}, + loadChildren: () => Promise.reject('Test async route loaded when it should not have been.') + }, + { + path: 'b', + component: DeepB + } +] diff --git a/examples/async-children/deep-async-default-a.js b/examples/async-children/deep-async-default-a.js new file mode 100644 index 000000000..d48aecd41 --- /dev/null +++ b/examples/async-children/deep-async-default-a.js @@ -0,0 +1,17 @@ +import Vue from 'vue' +import VueRouter from 'vue-router' + +const DeepA = { template: '
' } + +export const routes = [ + { + path: '', + component: DeepA, + loadChildren: () => import('./deep-async-default-b').then(asyncConfig => asyncConfig.routes) + }, + { + path: 'test', + component: {}, + loadChildren: () => Promise.reject('Test async route loaded when it should not have been.') + } +] diff --git a/examples/async-children/deep-async-default-b.js b/examples/async-children/deep-async-default-b.js new file mode 100644 index 000000000..ad533287b --- /dev/null +++ b/examples/async-children/deep-async-default-b.js @@ -0,0 +1,16 @@ +import Vue from 'vue' +import VueRouter from 'vue-router' + +const DeepB = { template: '
deep-async-default-b
' } + +export const routes = [ + { + path: '', + component: DeepB + }, + { + path: 'test', + component: {}, + loadChildren: () => Promise.reject('Test async route loaded when it should not have been.') + } +] diff --git a/examples/async-children/index.html b/examples/async-children/index.html new file mode 100644 index 000000000..4e5aa1549 --- /dev/null +++ b/examples/async-children/index.html @@ -0,0 +1,6 @@ + + +← Examples index +
+ + \ No newline at end of file diff --git a/flow/declarations.js b/flow/declarations.js index abfe7a5de..3d30d1ca2 100644 --- a/flow/declarations.js +++ b/flow/declarations.js @@ -61,7 +61,7 @@ declare type RouteConfig = { props?: boolean | Object | Function; caseSensitive?: boolean; pathToRegexpOptions?: PathToRegexpOptions; - loadChildren: string | LoadChildrenPromise; + loadChildren?: string | LoadChildrenPromise | null; } declare type RouteRecord = { @@ -76,8 +76,8 @@ declare type RouteRecord = { beforeEnter: ?NavigationGuard; meta: any; props: boolean | Object | Function | Dictionary; - loadChildren?: string | LoadChildrenPromise; - routeConfig?: RouteConfig; + loadChildren?: string | LoadChildrenPromise | null; + routeConfig: RouteConfig; } declare type Location = { diff --git a/src/create-matcher.js b/src/create-matcher.js index 481acfed4..7dbc8d63f 100644 --- a/src/create-matcher.js +++ b/src/create-matcher.js @@ -7,11 +7,12 @@ import { createRoute } from './util/route' import { fillParams } from './util/params' import { createRouteMap } from './create-route-map' import { normalizeLocation } from './util/location' +import { findParent, addChildren } from './util/async-children' export type Matcher = { match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route; - addRoutes: (routes: Array, parent?: string) => void; - loadAsyncChildren: (location: Route) => Promise; + addRoutes: (routes: Array) => void; + loadAsyncChildren: (raw: RawLocation, route: Route) => Promise; }; export function createMatcher ( @@ -20,12 +21,8 @@ export function createMatcher ( ): Matcher { const { pathList, pathMap, nameMap } = createRouteMap(routes) - function addRoutes (routes, parent) { - let parentRoute: RouteRecord - if (pathMap && parent) { - parentRoute = pathMap[parent] - } - createRouteMap(routes, pathList, pathMap, nameMap, parentRoute) + function addRoutes (routes) { + createRouteMap(routes, pathList, pathMap, nameMap) } function match ( @@ -77,9 +74,11 @@ export function createMatcher ( } function loadAsyncChildren( - location: Route - ): Promise { - const asyncMatches = location.matched.filter(match => match && !!match.loadChildren) + raw: RawLocation, + currentRoute: Route + ): Promise { + const location = normalizeLocation(raw, currentRoute, false, router) + const asyncMatches = currentRoute.matched.filter(match => match && !!match.loadChildren) if (!asyncMatches) { return Promise.reject(new Error('No matched routes have async children.')) } @@ -91,33 +90,15 @@ export function createMatcher ( : Promise.resolve([]) ) ]) - .then(children => { - for (let i = 0; i < children.length; i++) { - const {name, path} = asyncMatches[i] - const parentConfig = pathMap[path].routeConfig - const childRoutes: RouteConfig[] = children[1] - - if (!parentConfig) { - continue - } - - // Remove the loadChildren property - if (name) { - delete nameMap[name] - } - - delete pathMap[path] - delete parentConfig.loadChildren - - if (parentConfig.children) { - parentConfig.children.push(...childRoutes) - } else { - parentConfig.children = childRoutes - } - - // Load the children - addRoutes([parentConfig]) - } + .then(allChildren => { + return Promise.all([ + ...allChildren.map((children, i) => { + const {path} = asyncMatches[i] + const parent = findParent(pathMap[path]) + return addChildren(parent.routeConfig, children, path, location.path || '/') + .then(updatedConfig => addRoutes([updatedConfig])) + }) + ]) }) } diff --git a/src/create-route-map.js b/src/create-route-map.js index df0fe7f8e..6b59a65a3 100644 --- a/src/create-route-map.js +++ b/src/create-route-map.js @@ -50,6 +50,10 @@ function addRouteRecord ( ) { const { path, name } = route const hasAsyncChildren = typeof route.loadChildren === 'function' + const matchAllChildren = hasAsyncChildren && ( + (parent && path !== '' && path !== '/') || + !parent + ) if (process.env.NODE_ENV !== 'production') { assert(path != null, `"path" is required in a route configuration.`) @@ -82,7 +86,7 @@ function addRouteRecord ( const record: RouteRecord = { path: normalizedPath, - regex: compileRouteRegex(normalizedPath, pathToRegexpOptions, hasAsyncChildren), + regex: compileRouteRegex(normalizedPath, pathToRegexpOptions, matchAllChildren), components: route.components || { default: route.component }, instances: {}, name, @@ -154,8 +158,19 @@ function addRouteRecord ( pathMap[record.path] = record } + if (parent) { + // Ensure the parent route is after all child routes + const parentIndex = pathList.indexOf(parent.path) + const childIndex = pathList.indexOf(record.path) + + if (parentIndex < childIndex) { + pathList.push(pathList.splice(parentIndex, 1)[0]) + } + } + + if (name) { - if (!nameMap[name]) { + if (!nameMap[name] || nameMap[name].path === record.path) { nameMap[name] = record } else if (process.env.NODE_ENV !== 'production' && !matchAs) { warn( @@ -167,8 +182,8 @@ function addRouteRecord ( } } -function compileRouteRegex (path: string, pathToRegexpOptions: PathToRegexpOptions, async?: boolean): RouteRegExp { - const regex = Regexp(`${path}${async ? '(\/{0,1}.*)' : ''}`, [], pathToRegexpOptions) +function compileRouteRegex (path: string, pathToRegexpOptions: PathToRegexpOptions, matchAllChildren?: boolean): RouteRegExp { + const regex = Regexp(`${path}${matchAllChildren ? '(\/{0,1}.*)' : ''}`, [], pathToRegexpOptions) if (process.env.NODE_ENV !== 'production') { const keys: any = {} regex.keys.forEach(key => { diff --git a/src/history/base.js b/src/history/base.js index ccb27c496..fc5885564 100644 --- a/src/history/base.js +++ b/src/history/base.js @@ -61,16 +61,14 @@ export class History { this.errorCbs.push(errorCb) } - transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function, childrenLoaded?: boolean) { + transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) { const route = this.router.match(location, this.current) this.confirmTransition(route, () => { - this.updateRoute(route) - - if (route.loadChildren && !childrenLoaded) { - this.router.matcher.loadAsyncChildren(route) + if (route.loadChildren) { + this.router.matcher.loadAsyncChildren(location, route) .then(() => { // Perform transition again to ensure the proper component hierarchy is loaded - this.transitionTo(location, onComplete, onAbort, true) + this.transitionTo(location, onComplete, onAbort) }) .catch((err) => { if (onAbort) { @@ -83,6 +81,7 @@ export class History { } }) } else { + this.updateRoute(route) onComplete && onComplete(route) this.ensureURL() diff --git a/src/index.js b/src/index.js index 12285a4fc..a1a7b8507 100644 --- a/src/index.js +++ b/src/index.js @@ -206,8 +206,8 @@ export default class VueRouter { } } - addRoutes (routes: Array, parent?: string) { - this.matcher.addRoutes(routes, parent) + addRoutes (routes: Array) { + this.matcher.addRoutes(routes) if (this.history.current !== START) { this.history.transitionTo(this.history.getCurrentLocation()) } diff --git a/src/util/async-children.js b/src/util/async-children.js new file mode 100644 index 000000000..6caf10a52 --- /dev/null +++ b/src/util/async-children.js @@ -0,0 +1,84 @@ +/* @flow */ + +export function findParent( + routeRecord: RouteRecord +): RouteRecord { + if (routeRecord.parent) { + return findParent(routeRecord.parent) + } else { + return routeRecord + } +} + +export function loadDefaultAsyncChildren( + childrenConfigs: Array +): Promise { + return Promise.all([ + ...childrenConfigs.map(child => { + if (child.path === '' && typeof child.loadChildren === 'function') { + return child.loadChildren() + .then(asyncChildren => + loadDefaultAsyncChildren(asyncChildren) + ) + .then(asyncChildren => { + child.loadChildren = null + + if (child.children) { + child.children.push(...asyncChildren) + } else { + child.children = asyncChildren + } + + return child + }) + } else { + return child + } + }) + ]) +} + +export function addChildren( + routeConfig: RouteConfig, + children: RouteConfig[], + childRoutePath: string, + location: string +): Promise { + let updatedPath = childRoutePath.replace(/^\//, '') + let configPath = routeConfig.path.replace(/^\//, '') + let updatedLocation = location.replace(/^\//, '').replace(configPath, '') + + if (updatedPath === configPath) { + routeConfig.loadChildren = null + if (routeConfig.children) { + routeConfig.children.push(...children) + } else { + routeConfig.children = children + } + + if (updatedLocation === '') { + // User attempting to load default path + return loadDefaultAsyncChildren(routeConfig.children || []) + .then(asyncChildren => { + routeConfig.children = asyncChildren + return routeConfig + }) + } else { + return Promise.resolve(routeConfig) + } + } else if (updatedPath.indexOf(configPath) === 0) { + updatedPath = updatedPath.replace(configPath, '') + + return Promise.all([ + ...(routeConfig.children || []).map(child => + addChildren(child, children, updatedPath, location) + ) + ]) + .then(children => { + routeConfig.children = children + return routeConfig + }) + } else { + return Promise.resolve(routeConfig) + } +} \ No newline at end of file diff --git a/test/e2e/specs/async-children.js b/test/e2e/specs/async-children.js new file mode 100644 index 000000000..0b106e67d --- /dev/null +++ b/test/e2e/specs/async-children.js @@ -0,0 +1,70 @@ +module.exports = { + 'async children': function (browser) { + browser + .url('http://localhost:8080/async-children/') + .waitForElementVisible('#app', 1000) + .assert.count('li a', 8) + .assert.containsText('.view', 'home') + + .click('li:nth-child(2) a') + .assert.containsText('.view', 'foo') + + .click('li:nth-child(3) a') + .assert.containsText('.view', 'bar') + + .click('li:nth-child(1) a') + .assert.containsText('.view', 'home') + + .click('li:nth-child(4) a') + .waitForElementVisible('.async-view', 1000) + .assert.containsText('.async-view', 'basic-default') + + // test linking to children + .url('http://localhost:8080/async-children') + .waitForElementVisible('#app', 1000) + .click('li:nth-child(4) a') + .waitForElementVisible('.async-view', 1000) + .assert.containsText('.async-view', 'basic-default') + + .url('http://localhost:8080/async-children') + .waitForElementVisible('#app', 1000) + .click('li:nth-child(5) a') + .waitForElementVisible('.async-view', 1000) + .assert.containsText('.async-view', 'basic-foo') + + .url('http://localhost:8080/async-children') + .waitForElementVisible('#app', 1000) + .click('li:nth-child(6) a') + .waitForElementVisible('.async-view', 1000) + .assert.containsText('.async-view', 'basic-bar') + + // test deep linking to children + .url('http://localhost:8080/async-children/basic') + .waitForElementVisible('.async-view', 1000) + .assert.containsText('.async-view', 'basic-default') + + .url('http://localhost:8080/async-children/basic/foo') + .waitForElementVisible('.async-view', 1000) + .assert.containsText('.async-view', 'basic-foo') + + .url('http://localhost:8080/async-children/basic/bar') + .waitForElementVisible('.async-view', 1000) + .assert.containsText('.async-view', 'basic-bar') + + // test deep async loading + .url('http://localhost:8080/async-children') + .waitForElementVisible('#app', 1000) + .click('li:nth-child(7) a') + .waitForElementVisible('.async-viewA', 1000) + .assert.containsText('.async-viewA', 'deep-async-b') + + // test deep default async loading + .url('http://localhost:8080/async-children') + .waitForElementVisible('#app', 1000) + .click('li:nth-child(8) a') + .waitForElementVisible('.async-viewA', 1000) + .assert.containsText('.async-viewA', 'deep-async-b') + + .end() + } +} diff --git a/test/unit/specs/api.spec.js b/test/unit/specs/api.spec.js index 44f2f6c78..bb9894c66 100644 --- a/test/unit/specs/api.spec.js +++ b/test/unit/specs/api.spec.js @@ -93,15 +93,6 @@ describe('router.addRoutes', () => { components = router.getMatchedComponents() expect(components.length).toBe(0) - /** - * A given route represents a hierarchy of components loaded to the DOM - * where each parent must contain a `router-view` for it's children. - */ - router.addRoutes([{ path: 'b', component: { name: 'B' }}], '/a') - components = router.getMatchedComponents() - expect(components.length).toBe(2) - expect(components[1].name).toBe('B') - // make sure it preserves previous routes router.push('/a') components = router.getMatchedComponents() @@ -134,6 +125,26 @@ describe('router.push/replace callbacks', () => { } } + const AsyncFoo = { + beforeRouteEnter (to, from, next) { + calls.push(13) + setTimeout(() => { + calls.push(14) + next() + }, 1) + } + } + + const AsyncBar = { + beforeRouteEnter (to, from, next) { + calls.push(15) + setTimeout(() => { + calls.push(16) + next() + }, 1) + } + } + beforeEach(() => { calls = [] spy1 = jasmine.createSpy('complete') @@ -144,14 +155,24 @@ describe('router.push/replace callbacks', () => { { path: '/foo', component: Foo }, { path: '/asyncFoo', - name: 'asyncFoo', - component: Foo, + component: AsyncFoo, loadChildren: function () { return Promise.resolve([ { path: 'asyncBar', - name: 'asyncBar', - component: Bar + component: AsyncBar + } + ]) + } + }, + { + path: '/asyncBiz', + component: AsyncFoo, + loadChildren: function () { + return Promise.resolve([ + { + path: '', + component: AsyncBar } ]) } @@ -205,7 +226,7 @@ describe('router.push/replace callbacks', () => { describe('async children', function () { it('push complete', done => { router.push('/asyncFoo/asyncBar', () => { - expect(calls).toEqual([1, 2, 3, 4, 1, 2, 3, 4, 5, 6]) + expect(calls).toEqual([1, 2, 13, 14, 1, 2, 13, 14, 15, 16]) done() }) }) @@ -213,7 +234,7 @@ describe('router.push/replace callbacks', () => { it('push abort', done => { router.push('/foo', spy1, spy2) router.push('/asyncFoo/asyncBar', () => { - expect(calls).toEqual([1, 1, 2, 2, 3, 4, 1, 2, 3, 4, 5, 6]) + expect(calls).toEqual([1, 1, 2, 2, 13, 14, 1, 2, 13, 14, 15, 16]) expect(spy1).not.toHaveBeenCalled() expect(spy2).toHaveBeenCalled() done() @@ -222,7 +243,7 @@ describe('router.push/replace callbacks', () => { it('replace complete', done => { router.replace('/asyncFoo/asyncBar', () => { - expect(calls).toEqual([1, 2, 3, 4, 1, 2, 3, 4, 5, 6]) + expect(calls).toEqual([1, 2, 13, 14, 1, 2, 13, 14, 15, 16]) let components = router.getMatchedComponents() expect(components.length).toBe(2) @@ -233,7 +254,48 @@ describe('router.push/replace callbacks', () => { it('replace abort', done => { router.replace('/foo', spy1, spy2) router.replace('/asyncFoo/asyncBar', () => { - expect(calls).toEqual([1, 1, 2, 2, 3, 4, 1, 2, 3, 4, 5, 6]) + expect(calls).toEqual([1, 1, 2, 2, 13, 14, 1, 2, 13, 14, 15, 16]) + expect(spy1).not.toHaveBeenCalled() + expect(spy2).toHaveBeenCalled() + + let components = router.getMatchedComponents() + expect(components.length).toBe(2) + + done() + }) + }) + + it('push default complete', done => { + router.push('/asyncBiz', () => { + expect(calls).toEqual([1, 2, 13, 14, 1, 2, 13, 14, 15, 16]) + done() + }) + }) + + it('push default abort', done => { + router.push('/foo', spy1, spy2) + router.push('/asyncBiz', () => { + expect(calls).toEqual([1, 1, 2, 2, 13, 14, 1, 2, 13, 14, 15, 16]) + expect(spy1).not.toHaveBeenCalled() + expect(spy2).toHaveBeenCalled() + done() + }) + }) + + it('replace default complete', done => { + router.replace('/asyncBiz', () => { + expect(calls).toEqual([1, 2, 13, 14, 1, 2, 13, 14, 15, 16]) + + let components = router.getMatchedComponents() + expect(components.length).toBe(2) + done() + }) + }) + + it('replace default abort', done => { + router.replace('/foo', spy1, spy2) + router.replace('/asyncBiz', () => { + expect(calls).toEqual([1, 1, 2, 2, 13, 14, 1, 2, 13, 14, 15, 16]) expect(spy1).not.toHaveBeenCalled() expect(spy2).toHaveBeenCalled() From d57e4968ad422baa52dee0f85710b0c63df031c8 Mon Sep 17 00:00:00 2001 From: Patrick Housley Date: Sat, 8 Jul 2017 01:12:26 -0500 Subject: [PATCH 9/9] Lint and test fixes. --- examples/async-children/app.js | 6 +++--- examples/async-children/basic-async.js | 3 --- examples/async-children/deep-async-a.js | 3 --- examples/async-children/deep-async-b.js | 3 --- examples/async-children/deep-async-default-a.js | 3 --- examples/async-children/deep-async-default-b.js | 3 --- src/create-matcher.js | 4 ++-- src/create-route-map.js | 1 - src/util/async-children.js | 12 ++++++------ test/e2e/specs/async-children.js | 2 +- 10 files changed, 12 insertions(+), 28 deletions(-) diff --git a/examples/async-children/app.js b/examples/async-children/app.js index 1aed83b17..20e7e8c9e 100644 --- a/examples/async-children/app.js +++ b/examples/async-children/app.js @@ -1,7 +1,7 @@ import Vue from 'vue' import VueRouter from 'vue-router' -Vue.use(VueRouter); +Vue.use(VueRouter) const Home = { template: '
home
' } const Foo = { template: '
foo
' } @@ -32,7 +32,7 @@ const router = new VueRouter({ mode: 'history', base: __dirname, routes -}); +}) new Vue({ router, @@ -52,4 +52,4 @@ new Vue({ ` -}).$mount('#app') \ No newline at end of file +}).$mount('#app') diff --git a/examples/async-children/basic-async.js b/examples/async-children/basic-async.js index 9fcb2a3b6..9d43826b0 100644 --- a/examples/async-children/basic-async.js +++ b/examples/async-children/basic-async.js @@ -1,6 +1,3 @@ -import Vue from 'vue' -import VueRouter from 'vue-router' - const BasicDefault = { template: '
basic-default
' } const BasicFoo = { template: '
basic-foo
' } const BasicBar = { template: '
basic-bar
' } diff --git a/examples/async-children/deep-async-a.js b/examples/async-children/deep-async-a.js index 72d43d8da..0df8caf62 100644 --- a/examples/async-children/deep-async-a.js +++ b/examples/async-children/deep-async-a.js @@ -1,6 +1,3 @@ -import Vue from 'vue' -import VueRouter from 'vue-router' - const DeepA = { template: '
' } export const routes = [ diff --git a/examples/async-children/deep-async-b.js b/examples/async-children/deep-async-b.js index edb1fa003..d6dd56979 100644 --- a/examples/async-children/deep-async-b.js +++ b/examples/async-children/deep-async-b.js @@ -1,6 +1,3 @@ -import Vue from 'vue' -import VueRouter from 'vue-router' - const DeepB = { template: '
deep-async-b
' } export const routes = [ diff --git a/examples/async-children/deep-async-default-a.js b/examples/async-children/deep-async-default-a.js index d48aecd41..981d49041 100644 --- a/examples/async-children/deep-async-default-a.js +++ b/examples/async-children/deep-async-default-a.js @@ -1,6 +1,3 @@ -import Vue from 'vue' -import VueRouter from 'vue-router' - const DeepA = { template: '
' } export const routes = [ diff --git a/examples/async-children/deep-async-default-b.js b/examples/async-children/deep-async-default-b.js index ad533287b..5fdbe7866 100644 --- a/examples/async-children/deep-async-default-b.js +++ b/examples/async-children/deep-async-default-b.js @@ -1,6 +1,3 @@ -import Vue from 'vue' -import VueRouter from 'vue-router' - const DeepB = { template: '
deep-async-default-b
' } export const routes = [ diff --git a/src/create-matcher.js b/src/create-matcher.js index 7dbc8d63f..870825de7 100644 --- a/src/create-matcher.js +++ b/src/create-matcher.js @@ -73,7 +73,7 @@ export function createMatcher ( return _createRoute(null, location) } - function loadAsyncChildren( + function loadAsyncChildren ( raw: RawLocation, currentRoute: Route ): Promise { @@ -93,7 +93,7 @@ export function createMatcher ( .then(allChildren => { return Promise.all([ ...allChildren.map((children, i) => { - const {path} = asyncMatches[i] + const { path } = asyncMatches[i] const parent = findParent(pathMap[path]) return addChildren(parent.routeConfig, children, path, location.path || '/') .then(updatedConfig => addRoutes([updatedConfig])) diff --git a/src/create-route-map.js b/src/create-route-map.js index 6b59a65a3..b986f5427 100644 --- a/src/create-route-map.js +++ b/src/create-route-map.js @@ -167,7 +167,6 @@ function addRouteRecord ( pathList.push(pathList.splice(parentIndex, 1)[0]) } } - if (name) { if (!nameMap[name] || nameMap[name].path === record.path) { diff --git a/src/util/async-children.js b/src/util/async-children.js index 6caf10a52..c367aa7e9 100644 --- a/src/util/async-children.js +++ b/src/util/async-children.js @@ -1,6 +1,6 @@ /* @flow */ -export function findParent( +export function findParent ( routeRecord: RouteRecord ): RouteRecord { if (routeRecord.parent) { @@ -10,7 +10,7 @@ export function findParent( } } -export function loadDefaultAsyncChildren( +export function loadDefaultAsyncChildren ( childrenConfigs: Array ): Promise { return Promise.all([ @@ -38,15 +38,15 @@ export function loadDefaultAsyncChildren( ]) } -export function addChildren( +export function addChildren ( routeConfig: RouteConfig, children: RouteConfig[], childRoutePath: string, location: string ): Promise { let updatedPath = childRoutePath.replace(/^\//, '') - let configPath = routeConfig.path.replace(/^\//, '') - let updatedLocation = location.replace(/^\//, '').replace(configPath, '') + const configPath = routeConfig.path.replace(/^\//, '') + const updatedLocation = location.replace(/^\//, '').replace(configPath, '') if (updatedPath === configPath) { routeConfig.loadChildren = null @@ -81,4 +81,4 @@ export function addChildren( } else { return Promise.resolve(routeConfig) } -} \ No newline at end of file +} diff --git a/test/e2e/specs/async-children.js b/test/e2e/specs/async-children.js index 0b106e67d..6696c1ab6 100644 --- a/test/e2e/specs/async-children.js +++ b/test/e2e/specs/async-children.js @@ -63,7 +63,7 @@ module.exports = { .waitForElementVisible('#app', 1000) .click('li:nth-child(8) a') .waitForElementVisible('.async-viewA', 1000) - .assert.containsText('.async-viewA', 'deep-async-b') + .assert.containsText('.async-viewA', 'deep-async-default-b') .end() }