如何为服务器呈现的惰性路由构建Vue 2.0应用程序?

时间:2016-10-27 22:29:05

标签: node.js webpack vue.js isomorphic-javascript vue-router

我尝试根据以下链接中的说明修改vue-hackernews-2.0以使用Webpack code-splitting feature支持延迟加载的路由:

然而,我遇到了一些问题。当我在浏览器中加载应用程序时,在尝试加载服务器端块时,所有建议的语法变体都会在服务器端触发Module not found错误。

考虑到router.js ...

中代码分割点的这个包装器
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

// INSERT CODE-SPLIT POINT SYNTAXES HERE (they are below)

export default new Router({
  mode: 'history',
  routes: [{
    path: '/',
    component: Home
  }, {
    path: '/foo',
    component: Foo
  }]
})

所有这些语法变体都引发了Module not found错误:

变体1:

const Home = () => System.import('./views/Home.vue')
const Foo = () => System.import('./views/Foo.vue')

变体2:

const Home = (resolve) => require(['./views/Home.vue'], resolve)
const Foo = (resolve) => require(['./views/Foo.vue'], resolve)

变体3:

const Home = (resolve) => {
  require.ensure(['./views/Home.vue'], () => {
    resolve(require('./views/Home.vue'))
  })
}

const Foo = (resolve) => {
  require.ensure(['./views/Foo.vue'], () => {
    resolve(require('./views/Foo.vue'))
  })
}

错误信息始终如下:
(注意:此错误改编自我对该问题的一个小型复制,来自hackernews示例)

Error: Cannot find module './0.server.js'
  at Function.Module._resolveFilename (module.js:440:15)
  at Function.Module._load (module.js:388:25)
  at Module.require (module.js:468:17)
  at require (internal/module.js:20:19)
  at Function.requireEnsure [as e] (__vue_ssr_bundle__:42:25)
  at Home (__vue_ssr_bundle__:152:30)
  at /Users/razorbeard/projects/vue-2-ssr/node_modules/vue-router/dist/vue-router.js:1421:19
  at iterator (/Users/razorbeard/projects/vue-2-ssr/node_modules/vue-router/dist/vue-router.js:1277:5)
  at step (/Users/razorbeard/projects/vue-2-ssr/node_modules/vue-router/dist/vue-router.js:1213:9)
  at step (/Users/razorbeard/projects/vue-2-ssr/node_modules/vue-router/dist/vue-router.js:1217:9)

我尝试调整我的代码以使用Server-side react with webpack 2 System.import提供的建议,但这些建议也不起作用。

我读了一篇描述使用Webpack DefinePlugin构建构建时全局变量的post,它允许我检查代码是在服务器上还是在客户端上运行 - 这个允许我在客户端上进行代码拆分,但只需将所有内容捆绑在服务器上。

在服务器webpack配置中:

{
  ...
  plugins: [
    new webpack.DefinePlugin({
      BROWSER_BUILD: false
    })
  ]
  ...
}

在客户端webpack配置中:

{
 ...
 plugins: [
   new webpack.DefinePlugin({
     BROWSER_BUILD: true
   })
 ]
 ...
}

然后,在与router.js文件相同的包装片段中,我使用了这种语法变体:

const Home = BROWSER_BUILD ? () => System.import('./views/Home.vue') : require('./views/Home.vue')
const Foo = BROWSER_BUILD ? () => System.import('./views/Foo.vue') : require('./views/Foo.vue')

这使渲染工作 - 部分。直接导航到浏览器中的应用程序(和相应的路径)服务器呈现正确的UI。点击,vue-router的客户端逻辑将我带到了正确的用户界面。一切似乎都很糟糕 - 直到我打开DevTools:

error in devtools about mismatched DOM and VNode tree

如果模块作为路径的子组件延迟加载,也会出现同样的问题:

<template>
  <div class="page">
    <heading></heading>
  </div>
</template>

<script>
  const Heading = BROWSER_BUILD ? () => System.import('./Heading.vue') : require('./Heading.vue')
  export default {
    components: {
      Heading
    }
  }
</script>

我试图在官方的Vue论坛上寻求帮助,但空洞地说:http://forum.vuejs.org/t/2-0-help-needed-with-server-rendered-lazy-routes/906

认为这可能是vue-router的错误,我在那里打开了一个问题:https://github.com/vuejs/vue-router/issues/820

不幸的是,我无法找到解决方案。

所以,我整理了一个小型仓库,重现了这个问题:https://github.com/declandewet/vue2-ssr-lazy-error

我预感到实际问题可能来自https://www.npmjs.com/package/vue-server-renderer

我真的坚持这一点并习惯于做出反应是多么容易 - 并且非常感谢任何有关解决方案的帮助/提示/方向!

为方便起见,这是来自复制回购的webpack配置:

import fs from 'fs'
import path from 'path'
import webpack from 'webpack'
import validate from 'webpack2-validator'
import { dependencies } from './package.json'

let babelConfig = JSON.parse(fs.readFileSync('./.babelrc'))

/* turn off modules in es2015 preset to enable tree-shaking
   (this is on in babelrc because setting it otherwise causes issues with
   this config file) */
babelConfig.presets = babelConfig.presets.map(
  (preset) => preset === 'es2015' ? ['es2015', { modules: false }] : preset
)

const babelOpts = {
  ...babelConfig,
  babelrc: false,
  cacheDirectory: 'babel_cache'
}

const SHARED_CONFIG = {
  devtool: 'source-map',
  module: {
    loaders: [{
      test: /\.vue$/,
      loader: 'vue'
    }, {
      test: /\.js$/,
      loader: 'babel',
      exclude: 'node_modules',
      query: babelOpts
    }]
  },
  resolve: {
    modules: [
      path.join(__dirname, './src'),
      'node_modules'
    ]
  }
}

const SERVER_CONFIG = validate({
  ...SHARED_CONFIG,
  target: 'node',
  entry: {
    server: './src/server.js',
    renderer: './src/renderer.js'
  },
  output: {
    path: path.join(__dirname, './dist'),
    filename: '[name].js',
    chunkFilename: '[id].server.js',
    libraryTarget: 'commonjs2'
  },
  plugins: [
    new webpack.DefinePlugin({
      BROWSER_BUILD: false,
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
    }),
    new webpack.BannerPlugin({
      banner: 'require("source-map-support").install();',
      raw: true,
      entryOnly: false
    })
  ],
  externals: Object.keys(dependencies)
})

const CLIENT_CONFIG = validate({
  ...SHARED_CONFIG,
  entry: {
    app: './src/client.js',
    vendor: ['vue']
  },
  output: {
    path: path.join(__dirname, './dist/assets'),
    publicPath: '/',
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.DefinePlugin({
      BROWSER_BUILD: true,
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      filename: 'vendor.js'
    })
  ]
})

export default [SERVER_CONFIG, CLIENT_CONFIG]

编辑:注意到在React中,我们在客户端使用match为当前视图获取正确的路由配置,我决定使用{{检查哪些组件匹配1}}并找到了一些有趣的东西:

服务器条目:

app.$router.getMatchedComponents()

导航到主页时,会记录到终端:

import app from './app'

export default (context) => {
  // using app.$router instead of importing router itself works
  // (not sure why the hacker-news example imports the router module instead...)
  app.$router.push(context.url)
  const components = app.$router.getMatchedComponents()
  console.log('server-side', components)
  return Promise.all(components.map((component) => component))
    .then(() => app)
}

客户端输入:

server-side [ { __file: '/Users/razorbeard/projects/vue-2-ssr/src/views/Home.vue',
    render: [Function],
    staticRenderFns: [ [Function] ] } ]

导航到主页时,会记录到devtools控制台:

import app from './app'

const components = app.$router.getMatchedComponents()

console.log('client-side', components)

// kickoff client-side hydration
Promise.all(components.map((component) => Promise.resolve(component)))
  .then(() => app.$mount('#app'))

如您所见,客户端没有任何组件匹配。

0 个答案:

没有答案