6

我正在尝试创建一个 @vue/cli3 SSR 项目,但似乎无法生成服务器捆绑文件。客户端构建良好。

package.json 脚本

"scripts": {
    "serve": "npm run build && node scripts/serve",
    "build": "npm run build:server && mv dist/vue-ssr-server-bundle.json bundle && npm run build:client && mv bundle dist/vue-ssr-server-bundle.json",
    "build:client": "vue-cli-service build",
    "build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build"
}

运行出错yarn serve

$ yarn serve
yarn run v1.9.2
$ npm run build && node scripts/serve

> project-name@0.1.0 build /path/to/file/project-name
> npm run build:server && mv dist/vue-ssr-server-bundle.json bundle && npm run build:client && mv bundle dist/vue-ssr-server-bundle.json


> project-name@0.1.0 build:server /path/to/file/project-name
> cross-env WEBPACK_TARGET=node vue-cli-service build


⠙  Building for production...(node:24514) DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead
⠏  Building for production...(node:24514) UnhandledPromiseRejectionWarning: Error: Server-side bundle should have one single entry file. Avoid using CommonsChunkPlugin in the server config.
    at /path/to/file/project-name/node_modules/vue-server-renderer/server-plugin.js:58:13
    at _err2 (eval at create (/path/to/file/project-name/node_modules/tapable/lib/HookCodeFactory.js:24:12), <anonymous>:22:1)
    at callback (/path/to/file/project-name/node_modules/copy-webpack-plugin/dist/index.js:77:17)
    at /path/to/file/project-name/node_modules/copy-webpack-plugin/dist/index.js:118:24
    at <anonymous>
(node:24514) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:24514) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

vue.config.js

const path = require('path')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
const nodeExternals = require('webpack-node-externals')
const merge = require('lodash.merge')

const TARGET_NODE = process.env.WEBPACK_TARGET === 'node'

const resolve = (file) => path.resolve(__dirname, file)

const target = TARGET_NODE
  ? 'server'
  : 'client'

module.exports = {
  configureWebpack: () => ({
    entry: {
      app: `./src/entry-${target}`
    },
    target: TARGET_NODE ? 'node' : 'web',
    node: TARGET_NODE ? undefined : false,
    plugins: [
      TARGET_NODE
        ? new VueSSRServerPlugin()
        : new VueSSRClientPlugin()
    ],
    externals: TARGET_NODE ? nodeExternals({
      whitelist: /\.css$/
    }) : undefined,
    output: {
      libraryTarget: TARGET_NODE
        ? 'commonjs2'
        : undefined
    },
    optimization: {
      splitChunks: undefined
    },
    resolve:{
      alias: {
        '@': resolve('src'),
        'public': resolve('public')
      }
    }
  }),
  chainWebpack: config => {
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap(options =>
        merge(options, {
          optimizeSSR: false
        })
      )
  }
}

最后,server.js 文件:

/* eslint-disable no-console */

const fs = require('fs')
const path = require('path')
const express = require('express')
const compression = require('compression')
const favicon = require('serve-favicon')
const microcache = require('route-cache')
const Ouch = require('ouch')
var proxy = require('http-proxy-middleware')
const { createBundleRenderer } = require('vue-server-renderer')

const resolve = file => path.resolve(__dirname, file)

const devServerBaseURL = process.env.DEV_SERVER_BASE_URL || 'http://localhost'
const devServerPort = process.env.DEV_SERVER_PORT || 8080
const isProd = process.env.NODE_ENV === 'production'
const useMicroCache = process.env.MICRO_CACHE !== 'false'

const serverInfo =
  `express/${require('express/package.json').version} ` +
  `vue-server-renderer/${require('vue-server-renderer/package.json').version}`

const app = express()

function createRenderer (bundle, options) {
  return createBundleRenderer(bundle, Object.assign(options, {
    runInNewContext: false
  }))
}

let renderer
const templatePath = path.resolve(__dirname, './src/index.template.html')

const bundle = require('./dist/vue-ssr-server-bundle.json')
const template = fs.readFileSync(templatePath, 'utf-8')
const clientManifest = require('./dist/vue-ssr-client-manifest.json')
renderer = createRenderer(bundle, {
  template,
  clientManifest
})

if (process.env.NODE_ENV !== 'production') {
  app.use('/js/main*', proxy({
    target: `${devServerBaseURL}/${devServerPort}`,
    changeOrigin: true,
    pathRewrite: function (path) {
      return path.includes('main')
        ? '/main.js'
        : path
    },
    prependPath: false
  }))

  app.use('/*hot-update*', proxy({
    target: `${devServerBaseURL}/${devServerPort}`,
    changeOrigin: true
  }))

  app.use('/sockjs-node', proxy({
    target: `${devServerBaseURL}/${devServerPort}`,
    changeOrigin: true,
    ws: true
  }))
}

const serve = (path, cache) => express.static(resolve(path), {
  maxAge: cache && isProd ? 1000 * 60 * 60 * 24 * 30 : 0
})

app.use('/js', express.static(path.resolve(__dirname, './dist/js')))
app.use('/css', express.static(path.resolve(__dirname, './dist/css')))
app.use(express.json())
app.use(compression({ threshold: 0 }))
app.use(favicon('./public/favicon.ico'))
app.use('/public/manifest.json', serve('./manifest.json', true))
app.use('/public', serve('./public', true))
app.use('/public/robots.txt', serve('./robots.txt'))

app.get('/sitemap.xml', (req, res) => {
  res.setHeader('Content-Type', 'text/xml')
  res.sendFile(resolve('./public/sitemap.xml'))
})

// since this app has no user-specific content, every page is micro-cacheable.
// if your app involves user-specific content, you need to implement custom
// logic to determine whether a request is cacheable based on its url and
// headers.
// 10-minute microcache.
// https://www.nginx.com/blog/benefits-of-microcaching-nginx/
const cacheMiddleware = microcache.cacheSeconds(10 * 60, req => useMicroCache && req.originalUrl)

const ouchInstance = (new Ouch()).pushHandler(new Ouch.handlers.PrettyPageHandler('orange', null, 'sublime'))

function render (req, res) {
  const start = Date.now()

  res.setHeader('Content-Type', 'text/html')
  res.setHeader('Server', serverInfo)

  const context = {
    url: req.url,
    res
  }

  const handleError = err => {
    if (err.url) {
      res.redirect(err.url)
    } else if (err.code === 404) {
      res.status(404).send('404 | Page Not Found')
    } else {
      ouchInstance.handleException(err, req, res, output => {
        console.log(JSON.stringify(err))
        console.log('Error handled!')
      })
    }
  }

  renderer.renderToString(context, (err, html) => {
    if (err) return handleError(err)

    res.end(html)

    !isProd && console.log(`whole request: ${Date.now() - start}ms`)
  })
}

app.get('*', render, cacheMiddleware)

const port = process.env.PORT || 8095
const host = process.env.HOST || '0.0.0.0'
app.listen(port, host, () => {
  console.log(`server started at ${host}:${port}`)
})

似乎此错误来自 vue-server-renderer,并且与https://github.com/vuejs/vue/issues/5553有关,但它不需要提供任何关于如何解决此问题的线索。所有的 Promise 都有 catch 块,所以不应该处理任何拒绝。这仅在WEBPACK_TARGET === 'node'使用 VueSSRServerPlugin 时才会发生。

4

1 回答 1

0

我是这样解决的:

const base = require("@vue/cli-service/webpack.config");
delete base.optimization;

module.exports = merge(base, {
   ... config related to SSR ...
});
于 2019-12-23T10:26:05.843 回答