Skip to content

Commit

Permalink
feat: auto resolve ES module default when resolving async components
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jun 29, 2017
1 parent e55c89b commit d539788
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 98 deletions.
104 changes: 6 additions & 98 deletions src/history/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@

import { _Vue } from '../install'
import type Router from '../index'
import { warn } from '../util/warn'
import { inBrowser } from '../util/dom'
import { runQueue } from '../util/async'
import { warn, isError } from '../util/warn'
import { START, isSameRoute } from '../util/route'
import {
flatten,
flatMapComponents,
resolveAsyncComponents
} from '../util/resolve-components'

export class History {
router: Router;
Expand Down Expand Up @@ -321,100 +326,3 @@ function poll (
}, 16)
}
}

function resolveAsyncComponents (matched: Array<RouteRecord>): Function {
return (to, from, next) => {
let hasAsync = false
let pending = 0
let error = null

flatMapComponents(matched, (def, _, match, key) => {
// if it's a function and doesn't have cid attached,
// assume it's an async component resolve function.
// we are not using Vue's default async resolving mechanism because
// we want to halt the navigation until the incoming component has been
// resolved.
if (typeof def === 'function' && def.cid === undefined) {
hasAsync = true
pending++

const resolve = once(resolvedDef => {
// save resolved on async factory in case it's used elsewhere
def.resolved = typeof resolvedDef === 'function'
? resolvedDef
: _Vue.extend(resolvedDef)
match.components[key] = resolvedDef
pending--
if (pending <= 0) {
next()
}
})

const reject = once(reason => {
const msg = `Failed to resolve async component ${key}: ${reason}`
process.env.NODE_ENV !== 'production' && warn(false, msg)
if (!error) {
error = isError(reason)
? reason
: new Error(msg)
next(error)
}
})

let res
try {
res = def(resolve, reject)
} catch (e) {
reject(e)
}
if (res) {
if (typeof res.then === 'function') {
res.then(resolve, reject)
} else {
// new syntax in Vue 2.3
const comp = res.component
if (comp && typeof comp.then === 'function') {
comp.then(resolve, reject)
}
}
}
}
})

if (!hasAsync) next()
}
}

function flatMapComponents (
matched: Array<RouteRecord>,
fn: Function
): Array<?Function> {
return flatten(matched.map(m => {
return Object.keys(m.components).map(key => fn(
m.components[key],
m.instances[key],
m, key
))
}))
}

function flatten (arr) {
return Array.prototype.concat.apply([], arr)
}

// in Webpack 2, require.ensure now also returns a Promise
// so the resolve/reject functions may get called an extra time
// if the user uses an arrow function shorthand that happens to
// return that Promise.
function once (fn) {
let called = false
return function (...args) {
if (called) return
called = true
return fn.apply(this, args)
}
}

function isError (err) {
return Object.prototype.toString.call(err).indexOf('Error') > -1
}
100 changes: 100 additions & 0 deletions src/util/resolve-components.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/* @flow */

import { _Vue } from '../install'
import { warn, isError } from './warn'

export function resolveAsyncComponents (matched: Array<RouteRecord>): Function {
return (to, from, next) => {
let hasAsync = false
let pending = 0
let error = null

flatMapComponents(matched, (def, _, match, key) => {
// if it's a function and doesn't have cid attached,
// assume it's an async component resolve function.
// we are not using Vue's default async resolving mechanism because
// we want to halt the navigation until the incoming component has been
// resolved.
if (typeof def === 'function' && def.cid === undefined) {
hasAsync = true
pending++

const resolve = once(resolvedDef => {
if (resolvedDef.__esModule && resolvedDef.default) {
resolvedDef = resolvedDef.default
}
// save resolved on async factory in case it's used elsewhere
def.resolved = typeof resolvedDef === 'function'
? resolvedDef
: _Vue.extend(resolvedDef)
match.components[key] = resolvedDef
pending--
if (pending <= 0) {
next()
}
})

const reject = once(reason => {
const msg = `Failed to resolve async component ${key}: ${reason}`
process.env.NODE_ENV !== 'production' && warn(false, msg)
if (!error) {
error = isError(reason)
? reason
: new Error(msg)
next(error)
}
})

let res
try {
res = def(resolve, reject)
} catch (e) {
reject(e)
}
if (res) {
if (typeof res.then === 'function') {
res.then(resolve, reject)
} else {
// new syntax in Vue 2.3
const comp = res.component
if (comp && typeof comp.then === 'function') {
comp.then(resolve, reject)
}
}
}
}
})

if (!hasAsync) next()
}
}

export function flatMapComponents (
matched: Array<RouteRecord>,
fn: Function
): Array<?Function> {
return flatten(matched.map(m => {
return Object.keys(m.components).map(key => fn(
m.components[key],
m.instances[key],
m, key
))
}))
}

export function flatten (arr: Array<any>): Array<any> {
return Array.prototype.concat.apply([], arr)
}

// in Webpack 2, require.ensure now also returns a Promise
// so the resolve/reject functions may get called an extra time
// if the user uses an arrow function shorthand that happens to
// return that Promise.
function once (fn) {
let called = false
return function (...args) {
if (called) return
called = true
return fn.apply(this, args)
}
}
4 changes: 4 additions & 0 deletions src/util/warn.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ export function warn (condition: any, message: string) {
typeof console !== 'undefined' && console.warn(`[vue-router] ${message}`)
}
}

export function isError (err: any): boolean {
return Object.prototype.toString.call(err).indexOf('Error') > -1
}

0 comments on commit d539788

Please sign in to comment.