What causes this vue-router async component error - TypeError: undefined is not an object (evaluating 't.__esModule')?

What causes this vue-router async component error - TypeError: undefined is not an object (evaluating 't.__esModule')?

我注意到 Sentry 中有很多错误,堆栈跟踪如下:

TypeError: undefined is not an object (evaluating 't.__esModule')
  at isESModule(./node_modules/vue-router/dist/vue-router.esm.js:1955:3)
  at ? (./node_modules/vue-router/dist/vue-router.esm.js:1882:27)
  at promiseReactionJob([native code])

我自己重现错误并找出导致错误的原因时遇到了很多麻烦。查看vue-router源码,来自这个函数:

  function isESModule (obj) {
    return obj.__esModule || (hasSymbol && obj[Symbol.toStringTag] === 'Module')
  }

因此 obj 未定义。如果我们再往上一层,我们会得到这个函数:

  function resolveAsyncComponents (matched) {
    return function (to, from, next) {
      var hasAsync = false;
      var pending = 0;
      var error = null;

      flatMapComponents(matched, function (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++;

          var resolve = once(function (resolvedDef) {
            if (isESModule(resolvedDef)) {
              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();
            }
          });

...

所以看起来 resolvedDef 是未定义的。

我的猜测是 Vue 路由器成功解析了一个异步组件,但它最终未定义并且代码中没有任何内容可以解释这种情况。

我正在使用 vue-router 3.1.3,这些错误总是只在 iOS(Safari 或 Chrome)上发生。

我已经尝试使用谷歌搜索错误,但我似乎无法在其他任何地方找到对它的单一引用。我也无法 post vue-router Github 上的问题,因为我无法提供最小的复制。

Sentry 经常(但不总是)在错误之前显示此控制台日志:

console [vue-analytics] An error occured! Please check your connection or disable your AD blocker
logger console
extra {"arguments":["[vue-analytics] An error occured! Please check your connection or disable your AD blocker"]}

我不确定这是否相关。

我想到的一个疯狂的解决方案是部署 vue-router 的补丁版本,在 resolvedDef 未定义的情况下,它会抛出一个更有用的上下文错误。该错误有望最终出现在我们的 Sentry 日志中。

导致此错误的原因是什么?

我分叉了 vue-router 库并用这一行修补了 resolveAsyncComponents

if (!resolvedDef) {
  window.onerror(`router wtf ${matched.join(',')} ${def} ${_} ${match} ${key} ${pending} ${hasAsync} ${error}`);
}

最终在Sentry中出现如下错误:

router wtf [object Object] function(){return u(Promise.all([n.e("dashboard~orders~products~publicUser"),n.e("dashboard~discounts~products~publicUser"),n.e("orders~publicUser~tips"),n.e("publicUser~tips"),n.e("publicUser")]).then(n.bind(null,"89c6")))...

在生产中,我们的 Vue 应用程序似乎使用名为 bootstrap:

的启动器加载这些组件

然后我突然想到我们在 router.js 中的每个异步组件导入都使用了以下包装器:

      {
        path: 'dashboard',
        name: 'dashboard',
        component: () =>
          handleChunkLoadError(
            import(/* webpackChunkName: 'dashboard' */ '../views/Dashboard')
          ),
      },
function handleChunkLoadError(importPromise) {
  return importPromise.catch(() => {
    Vue.toasted.error('Network error encountered', {
      duration: null,
      action: {
        text: 'Reload',
        onClick: () => window.location.reload(),
      },
    });
  });
}

它应该显示一个允许用户在块加载失败时刷新页面的 toast。

但是我们忘记将错误传递给 vue-router:

function handleChunkLoadError(importPromise) {
  return importPromise.catch(error => {
    Vue.toasted.error('Network error encountered', {
      duration: null,
      action: {
        text: 'Reload',
        onClick: () => window.location.reload(),
      },
    });

    return error;
  });
}

返回 .catch 处理程序中的错误可解决问题。