From d539788df8394efe41c1534e1e9d1555aa2edbe2 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 29 Jun 2017 11:42:29 +0800 Subject: [PATCH] feat: auto resolve ES module default when resolving async components --- src/history/base.js | 104 ++------------------------------- src/util/resolve-components.js | 100 +++++++++++++++++++++++++++++++ src/util/warn.js | 4 ++ 3 files changed, 110 insertions(+), 98 deletions(-) create mode 100644 src/util/resolve-components.js diff --git a/src/history/base.js b/src/history/base.js index 0d7f3f697..0c90dafa7 100644 --- a/src/history/base.js +++ b/src/history/base.js @@ -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; @@ -321,100 +326,3 @@ function poll ( }, 16) } } - -function resolveAsyncComponents (matched: Array): 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, - fn: Function -): Array { - 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 -} diff --git a/src/util/resolve-components.js b/src/util/resolve-components.js new file mode 100644 index 000000000..78c4addc0 --- /dev/null +++ b/src/util/resolve-components.js @@ -0,0 +1,100 @@ +/* @flow */ + +import { _Vue } from '../install' +import { warn, isError } from './warn' + +export function resolveAsyncComponents (matched: Array): 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, + fn: Function +): Array { + 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): Array { + 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) + } +} diff --git a/src/util/warn.js b/src/util/warn.js index b0af7b876..65f1cec4f 100644 --- a/src/util/warn.js +++ b/src/util/warn.js @@ -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 +}