Subscribe on changes!

@vue/server-renderer's renderToString doesn't resolve async components

avatar
Mar 9th 2021

Version

3.0.7

Reproduction link

https://github.com/BeauAgst/async-resolve-vue

Steps to reproduce

Notice that in App.vue, we have used defineAsyncComponent. npm run compile is a small repro script to demonstrate that renderToString is failing.

  1. Run npm run build
  2. Run npm run compile

What is expected?

Asynchronous components should be resolved so that the component can render

What is actually happening?

[Vue warn]: Unhandled error during execution of setup function 
  at <AsyncComponentWrapper msg="Welcome to Your Vue.js App" >
(node:43827) UnhandledPromiseRejectionWarning: ReferenceError: document is not defined

Currently I'm trying to build out an app that will have a dynamic root component. I am running vue-cli-service build with 2 different contexts - one for server, and another for the client for hydration.

When I build for the server, it fails because of the async component. It works perfectly for the client however. If I use regular import, my client bundle becomes a lot larger, but the server bundle works and I can use renderToString.

I have also deleted the splitChunks optimisation via webpack.config.js, but that doesn't seem to resolve the issue.

I am able to get around this for my specific scenario by creating two versions of App.vue, which has async components for client, and regular imports for the server. However, this isn't ideal if I were to attempt to asynchronously load a component nested further into the project for the client.

avatar
Mar 9th 2021

Try this in your vue.config.js

const { WebpackManifestPlugin } = require('webpack-manifest-plugin')
const nodeExternals = require('webpack-node-externals')


module.exports = {
  chainWebpack: config => {
    config.plugin('manifest').use(new WebpackManifestPlugin({ fileName: 'manifest.json' }))

    if (process.env.BUILD_MODE !== 'SSR') return

    config.target('node')
    config.output.libraryTarget('commonjs2')

    config.externals(nodeExternals({ allowlist: /\.(css)$/ }))

    config.module.rule('vue').uses.delete('cache-loader')
    config.module.rule('js').uses.delete('cache-loader')
    config.module.rule('ts').uses.delete('cache-loader')
    config.module.rule('tsx').uses.delete('cache-loader')
    config.optimization.delete('splitChunks')
    config.plugins.delete('hmr').delete('html').delete('inline-runtime-chunk')
    config.plugins.delete('preload')
    config.plugins.delete('prefetch').delete('named-chunks')

    const isExtracting = config.plugins.has('extract-css')
    if (isExtracting) {
      const langs = ['css', 'postcss', 'scss', 'sass', 'less', 'stylus']
      const types = ['vue-modules', 'vue', 'normal-modules', 'normal']
      for (const lang of langs) {
        for (const type of types) {
          const rule = config.module.rule(lang).oneOf(type)
          rule.uses.delete('extract-css-loader')
        }
      }
      config.plugins.delete('extract-css')
    }
  },
}