Vuejs 2服务器端渲染 - 无法正常工作

时间:2017-01-13 22:22:35

标签: javascript vue.js vuejs2 vue-router serverside-rendering

我一直在努力使我的vuejs应用程序与SSR功能良好,但我的所有尝试都失败了。我真的需要帮助。

请注意我使用普通的js文件而不是使用es6的.vue文件,并且需要使用webpack require函数的html模板。

该应用程序在开发模式下正常工作,但是,当我开始使用' vue-server-renderer'并转到任何路线,这个错误将被抛出:

  

错误:组件中未定义渲染函数或模板:匿名       在normalizeRender(/Users/salaahassi/dev/vue/magicum/node_modules/vue-server-renderer/build.js:6015:13)       at renderComponent(/Users/salaahassi/dev/vue/magicum/node_modules/vue-server-renderer/build.js:6081:3)       at renderNode(/Users/salaahassi/dev/vue/magicum/node_modules/vue-server-renderer/build.js:6065:7)       在渲染(/Users/salaahassi/dev/vue/magicum/node_modules/vue-server-renderer/build.js:6257:5)       在RenderStream.render(/Users/salaahassi/dev/vue/magicum/node_modules/vue-server-renderer/build.js:6312:9)       在RenderStream.tryRender(/Users/salaahassi/dev/vue/magicum/node_modules/vue-server-renderer/build.js:96:12)       在RenderStream._read(/Users/salaahassi/dev/vue/magicum/node_modules/vue-server-renderer/build.js:125:12)       在RenderStream.Readable.read(_stream_readable.js:348:10)       在resume_(_stream_readable.js:737:12)       at _combinedTickCallback(internal / process / next_tick.js:74:11)

此外,当我在浏览器上禁用javascript时,即使主页也会消失(当然,因为它不能通过SSR工作)。

这是我的网站:

var path = require('path')
var webpack = require('webpack')
var HTMLPlugin = require('html-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var extractCSS = new ExtractTextPlugin('styles.css');

var options = {
  // entry: './entry.client.js',
  entry: {
    app: './entry.client.js',
    vendor: [
      'vue',
      'vue-router',
      'vuex',
      'vuex-router-sync',
      'moment',
      'axios'
    ]
  },
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/',
    filename: '[name].[hash].js',
  },
  module: {
    noParse: /es6-promise\.js$/, // avoid webpack shimming process
    rules: [
      {
        test: /\.html$/,
        loader: 'raw-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: /\.json$/,
        loader: 'json-loader'
      },
      {
        test: /\.(png|jpg|gif|svg|woff|woff2|eot|ttf)$/,
        loader: 'file-loader',
        options: {
          name: '[name].[ext]?[hash]'
        }
      },
      {
        test: /\.scss$/,
        loader: extractCSS.extract('css-loader!sass-loader')
      }
    ]
  },
  plugins: [
    extractCSS,
    new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/(en|zh-tw)$/),
    new webpack.DefinePlugin({
        'process.env': {
            'NODE_ENV': JSON.stringify(process.env.NODE_ENV) || 'development',
            'VUE_ENV': JSON.stringify(process.env.VUE_ENV) || 'client',
        }
    })
  ],
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue'
    }
  },
  devServer: {
    historyApiFallback: true,
    noInfo: true
  },
  devtool: '#eval-source-map'
}
console.log("xxxxx ---node env---- xxxx", process.env.NODE_ENV);
console.log("xxxxx ---vue env---- xxxx", process.env.VUE_ENV);
if (process.env.NODE_ENV != 'development') {
  options.entry = './entry.server.js';
  options.target = 'node';
  options.output.filename =  'bundle-server.js';
  options.output.libraryTarget =  'commonjs2';
  options.externals = Object.keys(require('./package.json').dependencies);
}

if (process.env.NODE_ENV == 'development') {
  options.plugins = (options.plugins || []).concat([
    new HTMLPlugin({
      template: './index.html'
    }),
    // extract vendor chunks for better caching
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor'
    })
  ]);
}

if (process.env.VUE_ENV == 'server') {
  options.devtool = '#source-map'
  options.plugins = (options.plugins || []).concat([
    new webpack.optimize.UglifyJsPlugin({
      //sourceMap: true,
      compress: {
        warnings: false
      }
    }),
    new webpack.LoaderOptionsPlugin({
      minimize: true
    }),
    new CopyWebpackPlugin([
      {from: './assets', to: 'assets'},
      {from: './index.html'}
    ])
  ])
}

module.exports = options;

这是我的服务器条目文件:

import { app, router, store } from './src/app'

export default context => {
  // set router's location
  router.push(context.url)
  // call prefetch hooks on components matched by the route
  const s = Date.now()
  return Promise.all(router.getMatchedComponents().map(component => {
    if (component.prefetch) {
      return component.prefetch(store)
    }
  })).then(() => {
    console.log(`data pre-fetch: ${Date.now() - s}ms`)
    // set initial store on context
    // the request handler will inline the state in the HTML response.
    context.initialState = store.state
    return app
  })
}

这是我的server.js:

'use strict'
const fs = require('fs')
const path = require('path')
const resolve = file => path.resolve(__dirname, file)
const express = require('express')
// const favicon = require('serve-favicon')
const serialize = require('serialize-javascript')

const createBundleRenderer = require('vue-server-renderer').createBundleRenderer

const app = express()

// parse index.html template
const template = fs.readFileSync(resolve('./dist/index.html'), 'utf-8')


// create server renderer from real fs
const bundlePath = resolve('./dist/bundle-server.js')
let renderer = createRenderer(fs.readFileSync(bundlePath, 'utf-8'))
console.log(renderer);
function createRenderer (bundle) {
  return createBundleRenderer(bundle, {
    cache: require('lru-cache')({
      max: 1000,
      maxAge: 1000 * 60 * 15
    })
  })
}

var options = {
    maxAge: '60d',
    setHeaders: function(res, path, stat) {
        // Webfonts need to have CORS * set in order to work.
        if (path.match(/ttf|woff|woff2|eot|svg/ig)) {
            res.set('Access-Control-Allow-Origin', '*');
        }
    }
};

var dist_path = '/dist/';
app.use(express.static(path.join(__dirname, dist_path), options));
console.log("............");
app.get('*', (req, res) => {
  console.log(".....ROUTE.......", req.url);
  console.log('renderer', renderer);
  if (!renderer) {
    return res.end('waiting for compilation... refresh in a moment.')
  }
  var s = Date.now()
  const context = { url: req.url }
  const renderStream = renderer.renderToStream(context)
  let firstChunk = true
  // console.log(html.head);
  // res.write(html.head)

  renderStream.on('data', chunk => {
    if (firstChunk) {
      // embed initial store state
      if (context.initialState) {
        res.write(
          `<script>window.__INITIAL_STATE__=${
            serialize(context.initialState, { isJSON: true })
          }</script>`
        )
      }
      firstChunk = false
    }
    res.write(chunk)
  })

  renderStream.on('end', () => {
    res.end(template)
    console.log(`whole request: ${Date.now() - s}ms`)
  })

  renderStream.on('error', err => {
    throw err
  })
})

const port = process.env.PORT || 3000
app.listen(port, () => {
  console.log(`server started at http://localhost:${port}`)
})

1 个答案:

答案 0 :(得分:5)

您的index.html模板是否包含占位符<!--vue-ssr-outlet-->

index.html