Vue.js 3 s-s-r - 客户端水合期间缺少模板或渲染功能
Posted
技术标签:
【中文标题】Vue.js 3 s-s-r - 客户端水合期间缺少模板或渲染功能【英文标题】:Vue.js 3 s-s-r - missing template or render function during client hydration 【发布时间】:2021-02-18 04:07:20 【问题描述】:我正在尝试创建 Vue.js 3 s-s-r 应用程序(包括 ts、@vue/cli、babel)。我使用 nodejs + express 作为后端。 s-s-r 工作正常(我正在从服务器正确呈现 html),但在客户端水合期间发生错误。似乎我的客户端构建不包含组件的模板,因为我在浏览器中遇到了这些错误
Vue warn]: Component is missing template or render function.
at <App>
at <App>
Vue warn]: Hydration node mismatch:
- Client vnode: Symbol(Comment)
- Server rendered DOM: <div class="hello-world">hello</div>
at <App>
at <App>
runtime-core.cjs.js:2942 Hydration completed but contains mismatches.
我发现当我用渲染函数替换模板时错误消失了,但它再次出现在子组件中(为了简单起见,HelloWorld没有子组件)。 这是my github repo,我在那里重现了我的问题。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
</head>
<body>
<div id="app">s-s-r_APP_CONTENT</div>
</body>
</html>
package.json
"name": "s-s-r3",
"version": "0.1.0",
"private": true,
"scripts":
"serve": "vue-cli-service serve",
"build": "rimraf dist && yarn build:server && yarn build:client",
"build:server": "cross-env s-s-r=true vue-cli-service build --dest dist/server --mode development",
"build:client": "vue-cli-service build --dest dist/client --mode development",
"lint": "vue-cli-service lint"
,
"dependencies":
"core-js": "^3.6.5",
"vue": "^3.0.0",
"vue-router": "^4.0.0-0",
"@vue/server-renderer": "^3.0.0",
"class-transformer": "^0.3.1",
"express": "^4.17.1",
"reflect-metadata": "^0.1.13",
"webpack-manifest-plugin": "^2.2.0",
"webpack-node-externals": "^2.5.2"
,
"devDependencies":
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^5.0.2",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-vue": "^7.0.0-0",
"node-sass": "^4.12.0",
"prettier": "^1.19.1",
"sass-loader": "^8.0.2",
"typescript": "~3.9.3",
"cross-env": "^7.0.2",
"husky": "^4.2.5",
"lint-staged": "^10.2.11"
,
"eslintConfig":
"root": true,
"env":
"node": true
,
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/typescript/recommended",
"@vue/prettier",
"@vue/prettier/@typescript-eslint"
],
"parserOptions":
"ecmaVersion": 2020
,
"rules":
,
"prettier":
"semi": false,
"singleQuote": true,
"tabWidth": 4,
"arrowParens": "avoid",
"printWidth": 100
,
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
vue.config.js
const ManifestPlugin = require('webpack-manifest-plugin')
const nodeExternals = require('webpack-node-externals')
const webpack = require('webpack')
module.exports =
configureWebpack:
resolve: mainFields: ['main', 'module']
,
chainWebpack: webpackConfig =>
const iss-s-r = process.env.s-s-r
webpackConfig
.entry('app')
.clear()
.add(iss-s-r ? './src/entry-server.ts' : './src/entry-client.ts')
webpackConfig.plugin('manifest').use(new ManifestPlugin( fileName: 'manifest.json' ))
if (!iss-s-r)
return
webpackConfig.target('node')
webpackConfig.output.libraryTarget('commonjs2')
webpackConfig.externals(nodeExternals( allowlist: /\.(css|vue)$/ ))
webpackConfig.optimization.splitChunks(false).minimize(false)
webpackConfig.plugins.delete('hmr')
webpackConfig.plugins.delete('preload')
webpackConfig.plugins.delete('prefetch')
webpackConfig.plugins.delete('progress')
webpackConfig.plugin('limit').use(
new webpack.optimize.LimitChunkCountPlugin(
maxChunks: 1
)
)
App.vue
<template>
<HelloWorld />
</template>
<script lang="ts">
import defineComponent from 'vue'
import HelloWorld from '@/components/HelloWorld.vue'
export default defineComponent(
components: HelloWorld
)
</script>
HelloWorld.vue
<template>
<div class="hello-world">hello</div>
</template>
<script lang="ts">
import defineComponent from 'vue'
export default defineComponent()
</script>
app.ts
import creates-s-rApp, h from 'vue'
import App from '@/App.vue'
export const createApp = () =>
const rootComponent =
render: () => h(App),
components: App
const app = creates-s-rApp(rootComponent)
return app
entry-server.ts
import createApp from '@/app'
export default async () =>
const app = createApp()
return app
entry-client.ts
import createApp from '@/app.ts'
const app = createApp()
app.mount('#app', true)
server.js
const path = require('path')
const fs = require('fs')
const express = require('express')
const renderToString = require('@vue/server-renderer')
const serverManifest = require('./dist/server/manifest.json')
const server = express()
const appPath = path.join(__dirname, '/dist/server', serverManifest['app.js'])
const createApp = require(appPath).default
server.use('/img', express.static(path.join(__dirname, '/dist/client', 'img')))
server.use('/js', express.static(path.join(__dirname, '/dist/client', 'js')))
server.use('/css', express.static(path.join(__dirname, '/dist/client', 'css')))
server.get(['/*'], async (req, res) =>
const app = await createApp()
const appContent = await renderToString(app)
fs.readFile(path.join(__dirname, '/dist/client/index.html'), (err, html) =>
if (err)
throw err
html = html.toString().replace('s-s-r_APP_CONTENT', `$appContent`)
res.setHeader('Content-Type', 'text/html')
res.send(html)
)
)
server.listen(80)
我的设置
- macOS Catalina 10.15.5
- Nodejs v12.19.0
- Google Chrome v86.0.4240.111
【问题讨论】:
【参考方案1】:我终于发现是cache-loader
导致了我的问题。
通过运行yarn build:server && yarn build:client
,客户端构建使用来自服务器构建的缓存组件,然后没有render
函数,因为s-s-r 构建只产生s-s-rRender
函数。
我已经通过在vue.config.js
中禁用chainWebpack
函数的缓存加载器来修复它。
chainWebpack: webpackConfig =>
...
webpackConfig.module.rule('vue').uses.delete('cache-loader')
webpackConfig.module.rule('js').uses.delete('cache-loader')
webpackConfig.module.rule('ts').uses.delete('cache-loader')
...
参考:https://forum.vuejs.org/t/disable-cache-loader-in-webpack-4-vue-cli-3/57561
【讨论】:
以上是关于Vue.js 3 s-s-r - 客户端水合期间缺少模板或渲染功能的主要内容,如果未能解决你的问题,请参考以下文章