Webpack4+vue+ssr打包后,访问路由出错

项目详情:一个Todo应用,有/app`(主页面)和/login(登录页面)两个路由


错误详情

在做ssr渲染时,我使用了路由懒加载,然后用webpack打包前端代码时,打包成功了,然后启动服务端端口时访问主页面出错了(但是生成环境没有问题,生产环境调试是使用webpack-dev-server + koa启动两个端口),生产环境访问出错原因是:

TypeError: Cannot read property 'call' of undefined
    at r (webpack/bootstrap:39:0)
    at Object.<anonymous> (client/views/todo/todo.vue?6149:6:0)
    at r (webpack/bootstrap:39:0)
    at Object.<anonymous> (client/views/todo/todo.vue?7847:6:0)
    at r (webpack/bootstrap:39:0)
    at VueComponent.s (client/views/todo/todo.vue:11:0)
    at VueComponent.c (node_modules/vue-loader/lib/runtime/component-normalizer.js:65:0)
    at callHook (D:\Editors\WebStorm\Files\20180423-imooc-vue-todo-further\node_modules\vue\dist\vue.runtime.common.js:2919:21)
    at VueComponent.Vue._init (D:\Editors\WebStorm\Files\20180423-imooc-vue-todo-further\node_modules\vue\dist\vue.runtime.common.js:4624:5)
    at new VueComponent (D:\Editors\WebStorm\Files\20180423-imooc-vue-todo-further\node_modules\vue\dist\vue.runtime.common.js:4796:12)

初步推断错误原因

我百度了很多解答, 可是都无效,我怀疑是组件懒加载出错了,因为Login组件懒加载没有问题,而Todo组件加载则出错,它们之间的区别是Login组件里面没有子组件,而Todo里面还有子组件

百思不得其解,望大佬们解答。

部分代码


下面是webpack打包的相关配置:

webpack基本配置baseConfig:

const baseConfig = {
  mode: process.env.NODE_ENV || 'production', // webpack 4 的配置,针对不同模式进行优化,必须
  target: 'web',
  entry: {
    app: path.join(__dirname, '../client/client-entry.js')
  },
  output: {
    path: path.join(__dirname, '../public'),
    filename: 'bundle.[hash:8].js',
    // 这里的配置需要加在devServer上的historyApiFallback中
    publicPath: 'http://127.0.0.1:8080/public/'
  },
  module: {
    rules: [
      {
        test: /\.(vue|jsx|js)$/,
        loader: 'eslint-loader',
        exclude: /node_modules/,
        enforce: 'pre' /* loader正式加载前先使用这个loader */
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: createVueLoaderOptions(isDev)
      },
      {
        test: /\.jsx$/,
        loader: 'babel-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: '/node_modules/'
      },
      {
        test: /\.(jpg|jpeg|png|svg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 1024,
              name: 'resources/[path][name].[hash:8].[ext]'
            }
          }
        ]
      }
    ]
  }
}

生成环境下的配置:

 // 生产环境
  config = merge(baseConfig, {
    mode: 'production',
    entry: {
      app: path.join(__dirname, '../client/client-entry.js')
    },
    output: {
      filename: '[name].[chunkhash:8].js',
      publicPath: '/public/'
    },
    module: {
      rules: [
        {
          test: /\.styl$/,
          use: ExtractPlugin.extract({
            /* style-loader包裹js代码,写入html,所以不需要写入 */
            fallback: 'style-loader',
            use: [
              'css-loader',
              {
                loader: 'postcss-loader',
                options: {
                  sourceMap: true
                }
              },
              'stylus-loader'
            ]
          })
        }
      ]
    },
    plugins: defaultPlugins.concat(new ExtractPlugin('styles.[chunkhash].css')),
    optimization: {
      splitChunks: {
        chunks: 'all'
      },
      runtimeChunk: true
    }
  })

下面是路由懒加载的部分代码:

// 访问/login没有问题,而访问/app出错
const Todo = () => import('../views/todo/todo.vue')
const Login = () => import('../views/login/login.vue')

// xxx

export default [
  {
    path: '/',
    redirect: '/app'
  },
  {
    path: '/app',
    component: Todo,
    name: 'app',
  },
  {
    path: '/login',
    component: Login
  }
]

如果不使用路由懒加载的话,webpack打包后,生产环境访问就没有问题。


// 不使用路由懒加载
import Todo from '../views/todo/todo.vue'
import Login from '../views/login/login.vue'

// xxx

export default [
  {
    path: '/',
    redirect: '/app'
  },
  {
    path: '/app',
    component: Todo,
    name: 'app',
  },
  {
    path: '/login',
    component: Login
  }
]

另外贴上login.vuetodo.vue组件的代码:


login.vue:

<template>
  <div>
    这是登录界面
  </div>
</template>

<script>
  export default {
    metaInfo: {
      title: 'Login - Todo App'
    }
  }
</script>

todo.vue:

<template>
  <section id="todo-wrapper" class="todo-wrapper">
    <input
      type="text"
      class="add-input"
      @keyup.enter="addTodo"
      autofocus="autofocus"
      placeholder="接下来要做什么?"
    >
    <Item
      v-for="todo in filterTodos"
      :todo="todo"
      :key="todo.id"
      @del="deleteTodo"
    ></Item>
    <Tabs :todos="todos"
          :filter="filter"
          @changeFilter="changeFilter"
          @clearCompleted="clearCompleted"></Tabs>
  </section>
</template>

<script>
  import Item from './item.vue'
  import Tabs from './tabs.vue'
  
  let id = 0
  
  export default {
    metaInfo: {
      title: 'The Todo App'
    },
    components: {
      Item,
      Tabs
    },
    props: ['id'],
    data () {
      return {
        filter: 'all',
        todos: []
      }
    },
    mounted () {
      console.log(this.$route)
      console.log('id: ', this.id)
    },
    computed: {
      filterTodos () {
        let state = this.filter
        if (state === 'all') {
          return this.todos
        } else if (state === 'active') {
          return this.todos.filter(todo => !todo.completed)
        } else {
          return this.todos.filter(todo => todo.completed)
        }
      }
    },
    methods: {
      addTodo (e) {
        this.todos.unshift({
          id: id++,
          content: e.target.value.trim(),
          completed: false
        })
        e.target.value = ''
      },
      deleteTodo (id) {
        this.todos.splice(this.todos.findIndex(todo => todo.id === id), 1)
      },
      clearCompleted () {
        this.todos = this.todos.filter(todo => !todo.completed)
      },
      changeFilter (state) {
        this.filter = state
      }
    }
  }
</script>

<style lang="stylus" scoped>
  .todo-wrapper {
    width 600px
    margin 0 auto
    padding 10px
    box-shadow 0 0 5px #666
    background-color #fff
  }
  
  .add-input {
    position relative
    width 100%
    padding 16px
    outline none
    border none
    font-size 24px
    line-height 40px
    box-shadow inset 0 -2px 1px rgba(0, 0, 0, 0.03)
  }

</style>

感谢。

阅读 3.4k
2 个回答

你好,我也遇到了类似的问题,也是不使用路由懒加载的话,webpack 打包后,生产环境访问就没有问题,请问你这个解决了嘛?

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题