首页 > 解决方案 > Vue服务器端渲染:beforeCreate钩子中的错误:ReferenceError:未定义文档

问题描述

添加文件<style></style>时会发生这种情况。.vue

[Vue warn]: Error in beforeCreate hook: "ReferenceError: document is not defined"

我主要根据教程网站编写代码。 https://github.com/vuejs/vue-hackernews-2.0/

src/App.vue

<template>
<div class="red">Hello from App.vue</div>
</template>
<script>
export default { name: "App" }
</script>
<style lang="scss" scoped> <-- Without style works well...
.red { color: red; }
</style>

src/app.js

import Vue from 'vue'
import App from './App.vue'

export function createApp() {
  let app = new Vue({
      render: h => h(App)
    })
  }
  return { app }
}

src/entry-server.js

import { createApp } from './app'
export default context => {
  return new Promise((resolve, reject) => {
    const { app } = createApp()
    resolve(app)
  })
}

src/entry-client.js

import { createApp } from './app'
const { app } = createApp()
app.$mount('#app')

webpack.config.js

const path = require('path')
const webpack = require('webpack')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')
const env = process.env.NODE_ENV || 'development'
const isProd = env === 'production'

const baseConfig = {
  mode: env,
  devtool: isProd
    ? false
    : 'source-map',
  output: {
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/dist/',
    filename: '[name].js'
  },
  module: {
    noParse: /es6-promise\.js$/,
    rules: [
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          'style-loader',
          'css-loader',
        ],
      },
      {
        test: /\.scss$/,
        use: [
          'vue-style-loader',
          'style-loader',
          'css-loader',
          'sass-loader'
        ],
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          compilerOptions: {
            preserveWhitespace: false
          }
        }
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: {
          presets: ['@babel/preset-env'],
        }
      },
      {
        test: /\.(png|jpg|gif|svg|jpeg)$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: '[name].[ext]?[hash]'
        }
      },
    ],
  },
  performance: {
    hints: false
  },
  plugins: isProd
  ? [
    new VueLoaderPlugin()
  ]
  : [
    new VueLoaderPlugin(),
    new FriendlyErrorsPlugin()
  ]
}

const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
const { merge } = require('webpack-merge')

const VueSSRClientConfig = merge(baseConfig, {
  entry: {
    app: './src/entry-client.js'
  },
  resolve: {
    alias: {
      'create-api': './create-api-client.js',
    },
    extensions: ['.js', '.vue']
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
      'process.env.VUE_ENV': '"client"'
    }),
    new VueSSRClientPlugin()
  ]
})

const nodeExternals = require("webpack-node-externals")
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')

const VueSSRServerConfig = merge(baseConfig, {
  target: 'node',
  entry: './src/entry-server.js',
  output: {
    filename: 'server-bundle.js',
    path: path.resolve(__dirname, 'dist'),
    libraryTarget: 'commonjs2'
  },
  resolve: {
    alias: {
      'create-api': './create-api-server.js',
    },
    extensions: ['.js', '.vue']
  },
  externals: nodeExternals({
    allowlist: /[\.css|\.scss]$/
  }),
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
      'process.env.VUE_ENV': '"server"'
    }),
    new VueSSRServerPlugin()
  ]
})

module.exports = [VueSSRServerConfig, VueSSRClientConfig]

setup-dev-server.js

const fs = require('fs')
const path = require('path')
const MFS = require('memory-fs')
const webpack = require('webpack')
const serverConfig = require('./webpack.config')[0]
const clientConfig = require('./webpack.config')[1]

const readFile = (fs, file) => {
  try {
    return fs.readFileSync(path.join(clientConfig.output.path, file), 'utf-8')
  } catch (e) {}
}

module.exports = function setupDevServer(app, cb) {
  let bundle
  let clientManifest

  let ready
  const readyPromise = new Promise(r => { ready = r })

  const update = () => {
    if (bundle && clientManifest) {
      ready()
      cb(bundle, { clientManifest })
    }
  }

  // modify client config to work with hot middleware
  clientConfig.entry.app = ['webpack-hot-middleware/client', clientConfig.entry.app]
  clientConfig.output.filename = '[name].js'
  clientConfig.plugins.push(
    new webpack.HotModuleReplacementPlugin(),
  )

  // dev middleware
  const clientCompiler = webpack(clientConfig)
  const devMiddleware = require('webpack-dev-middleware')(clientCompiler, {
    publicPath: clientConfig.output.publicPath,
    noInfo: true
  })

  app.use(devMiddleware)
  clientCompiler.hooks.done.tap('done', stats => {
    stats = stats.toJson()
    stats.errors.forEach(err => console.error(err))
    stats.warnings.forEach(err => console.warn(err))
    if (stats.errors.length) return
    clientManifest = JSON.parse(readFile(
      devMiddleware.fileSystem,
      'vue-ssr-client-manifest.json'
    ))
    update()
  })

  // hot middleware
  app.use(require('webpack-hot-middleware')(clientCompiler, { heartbeat: 1000 }))

  // watch and update server renderer
  const serverCompiler = webpack(serverConfig)
  const mfs = new MFS()
  serverCompiler.outputFileSystem = mfs
  serverCompiler.watch({}, (err, stats) => {
    if (err) throw err
    stats = stats.toJson()
    if (stats.errors.length) return

    bundle = JSON.parse(readFile(mfs, 'vue-ssr-server-bundle.json'))
    update()
  })

  return readyPromise
}

服务器.js

const path = require('path')
const express = require('express')
const app = express()
const resolve = file => path.resolve(__dirname, file)

const isProd = process.env.NODE_ENV === 'production'
const { createBundleRenderer } = require('vue-server-renderer')

function createRenderer(bundle, options) {
  return createBundleRenderer(bundle, Object.assign(options, {
    basedir: resolve('./dist'),
    runInNewContext: !isProd,
  }))
}

let renderer
let readyPromise

if (isProd) {
  const bundle = require('./dist/vue-ssr-server-bundle.json')
  const clientManifest = require('./dist/vue-ssr-client-manifest.json')

  renderer = createRenderer(bundle, { clientManifest })
} else {
  readyPromise = require('./setup-dev-server')(
    app,
    (bundle, options) => {
      renderer = createRenderer(bundle, options)
    }
  )
}

function render(req, res) {
  const context = req.body || {}
  const { requestId } = req.body || {}

  renderer.renderToString(context, (err, html) => {
    // return json
    res.json({ requestId, html })
  })
}

app.get('/', isProd ? render : (req, res) => {
  readyPromise.then(() => render(req, res))
})

app.listen(9991)

标签: vue.jswebpackserver-side-renderingvue-server-renderer

解决方案


很确定这与您的 webpack 配置有关。我认为这是因为样式加载器试图将您的样式注入 DOM(这显然不存在于服务器端)。因此参考误差。我不是 100% 确定,但尝试只使用 vue-style-loader。没有必要将它与 style-loader 放在一个链中,因为它们几乎在做同样的事情。

还要在项目上运行您的构建命令并查看服务器捆绑包。这将向您显示谁在尝试访问 DOM。

编辑:

作为您尝试做的一般方法,您还应该在一个规则中包含 sass/css,如下所示:

{
  test: /\.(sa|sc|c)ss$/,
  use: ['vue-style-loader', 'css-loader', 'sass-loader']
},

推荐阅读