Vue.js 3 SSR - 客户端水合作用期间缺少模板或渲染功能

Mar*_*nik 6 node.js typescript server-side-rendering vuejs3 vue-cli-4

我正在尝试创建 Vue.js 3 SSR 应用程序(包括 ts、@vue/cli、babel)。我使用 Nodejs + Express 作为后端。SSR 工作正常(我从服务器正确渲染了 html),但在客户端水合作用期间发生错误。我的客户端构建似乎不包含组件的模板,因为我在浏览器中收到这些错误

\n
Vue warn]: Component is missing template or render function. \n  at <App> \n  at <App>\n\nVue warn]: Hydration node mismatch:\n- Client vnode: Symbol(Comment) \n- Server rendered DOM: <div class=\xe2\x80\x8b"hello-world">\xe2\x80\x8bhello\xe2\x80\x8b</div>\xe2\x80\x8b  \n  at <App> \n  at <App>\n\nruntime-core.cjs.js:2942 Hydration completed but contains mismatches.\n
Run Code Online (Sandbox Code Playgroud)\n

我发现当我用渲染函数替换模板时错误消失了,但它再次出现在子组件上(为简单起见,HelloWorld没有子组件)。
\n这是我的 github 存储库,我在其中重现了我的问题。

\n

index.html

\n
Vue warn]: Component is missing template or render function. \n  at <App> \n  at <App>\n\nVue warn]: Hydration node mismatch:\n- Client vnode: Symbol(Comment) \n- Server rendered DOM: <div class=\xe2\x80\x8b"hello-world">\xe2\x80\x8bhello\xe2\x80\x8b</div>\xe2\x80\x8b  \n  at <App> \n  at <App>\n\nruntime-core.cjs.js:2942 Hydration completed but contains mismatches.\n
Run Code Online (Sandbox Code Playgroud)\n

package.json

\n
{\n    "name": "ssr3",\n    "version": "0.1.0",\n    "private": true,\n    "scripts": {\n        "serve": "vue-cli-service serve",\n        "build": "rimraf dist && yarn build:server && yarn build:client",\n        "build:server": "cross-env SSR=true vue-cli-service build --dest dist/server --mode development",\n        "build:client": "vue-cli-service build --dest dist/client --mode development",\n        "lint": "vue-cli-service lint"\n    },\n    "dependencies": {\n        "core-js": "^3.6.5",\n        "vue": "^3.0.0",\n        "vue-router": "^4.0.0-0",\n        "@vue/server-renderer": "^3.0.0",\n        "class-transformer": "^0.3.1",\n        "express": "^4.17.1",\n        "reflect-metadata": "^0.1.13",\n        "webpack-manifest-plugin": "^2.2.0",\n        "webpack-node-externals": "^2.5.2"\n    },\n    "devDependencies": {\n        "@typescript-eslint/eslint-plugin": "^2.33.0",\n        "@typescript-eslint/parser": "^2.33.0",\n        "@vue/cli-plugin-babel": "~4.5.0",\n        "@vue/cli-plugin-eslint": "~4.5.0",\n        "@vue/cli-plugin-router": "~4.5.0",\n        "@vue/cli-plugin-typescript": "~4.5.0",\n        "@vue/cli-service": "~4.5.0",\n        "@vue/compiler-sfc": "^3.0.0",\n        "@vue/eslint-config-prettier": "^6.0.0",\n        "@vue/eslint-config-typescript": "^5.0.2",\n        "eslint": "^6.7.2",\n        "eslint-plugin-prettier": "^3.1.3",\n        "eslint-plugin-vue": "^7.0.0-0",\n        "node-sass": "^4.12.0",\n        "prettier": "^1.19.1",\n        "sass-loader": "^8.0.2",\n        "typescript": "~3.9.3",\n        "cross-env": "^7.0.2",\n        "husky": "^4.2.5",\n        "lint-staged": "^10.2.11"\n    },\n    "eslintConfig": {\n        "root": true,\n        "env": {\n            "node": true\n        },\n        "extends": [\n            "plugin:vue/vue3-essential",\n            "eslint:recommended",\n            "@vue/typescript/recommended",\n            "@vue/prettier",\n            "@vue/prettier/@typescript-eslint"\n        ],\n        "parserOptions": {\n            "ecmaVersion": 2020\n        },\n        "rules": {}\n    },\n    "prettier": {\n        "semi": false,\n        "singleQuote": true,\n        "tabWidth": 4,\n        "arrowParens": "avoid",\n        "printWidth": 100\n    },\n    "browserslist": [\n        "> 1%",\n        "last 2 versions",\n        "not dead"\n    ]\n}\n
Run Code Online (Sandbox Code Playgroud)\n

vue.config.js

\n
const ManifestPlugin = require(\'webpack-manifest-plugin\')\nconst nodeExternals = require(\'webpack-node-externals\')\nconst webpack = require(\'webpack\')\n\nmodule.exports = {\n    configureWebpack: {\n        resolve: { mainFields: [\'main\', \'module\'] }\n    },\n\n    chainWebpack: webpackConfig => {\n        const isSSR = process.env.SSR\n        webpackConfig\n            .entry(\'app\')\n            .clear()\n            .add(isSSR ? \'./src/entry-server.ts\' : \'./src/entry-client.ts\')\n\n        webpackConfig.plugin(\'manifest\').use(new ManifestPlugin({ fileName: \'manifest.json\' }))\n\n        if (!isSSR) {\n            return\n        }\n\n        webpackConfig.target(\'node\')\n        webpackConfig.output.libraryTarget(\'commonjs2\')\n\n        webpackConfig.externals(nodeExternals({ allowlist: /\\.(css|vue)$/ }))\n\n        webpackConfig.optimization.splitChunks(false).minimize(false)\n\n        webpackConfig.plugins.delete(\'hmr\')\n        webpackConfig.plugins.delete(\'preload\')\n        webpackConfig.plugins.delete(\'prefetch\')\n        webpackConfig.plugins.delete(\'progress\')\n        webpackConfig.plugin(\'limit\').use(\n            new webpack.optimize.LimitChunkCountPlugin({\n                maxChunks: 1\n            })\n        )\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

App.vue

\n
<template>\n    <HelloWorld />\n</template>\n\n<script lang="ts">\n    import { defineComponent } from \'vue\'\n    import HelloWorld from \'@/components/HelloWorld.vue\'\n\n    export default defineComponent({\n        components: { HelloWorld }\n    })\n</script>\n
Run Code Online (Sandbox Code Playgroud)\n

HelloWorld.vue

\n
<template>\n    <div class="hello-world">hello</div>\n</template>\n\n<script lang="ts">\n    import { defineComponent } from \'vue\'\n    export default defineComponent({})\n</script>\n
Run Code Online (Sandbox Code Playgroud)\n

app.ts

\n
import { createSSRApp, h } from \'vue\'\nimport App from \'@/App.vue\'\n\nexport const createApp = () => {\n    const rootComponent = {\n        render: () => h(App),\n        components: { App }\n    }\n    const app = createSSRApp(rootComponent)\n    return { app }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

entry-server.ts

\n
import { createApp } from \'@/app\'\n\nexport default async () => {\n    const { app } = createApp()\n    return app\n}\n
Run Code Online (Sandbox Code Playgroud)\n

entry-client.ts

\n
import { createApp } from \'@/app.ts\'\n\nconst { app } = createApp()\napp.mount(\'#app\', true)\n
Run Code Online (Sandbox Code Playgroud)\n

server.js

\n
const path = require(\'path\')\nconst fs = require(\'fs\')\nconst express = require(\'express\')\nconst { renderToString } = require(\'@vue/server-renderer\')\nconst serverManifest = require(\'./dist/server/manifest.json\')\n\nconst server = express()\n\nconst appPath = path.join(__dirname, \'/dist/server\', serverManifest[\'app.js\'])\nconst createApp = require(appPath).default\n\nserver.use(\'/img\', express.static(path.join(__dirname, \'/dist/client\', \'img\')))\nserver.use(\'/js\', express.static(path.join(__dirname, \'/dist/client\', \'js\')))\nserver.use(\'/css\', express.static(path.join(__dirname, \'/dist/client\', \'css\')))\n\nserver.get([\'/*\'], async (req, res) => {\n    const app = await createApp()\n    const appContent = await renderToString(app)\n\n    fs.readFile(path.join(__dirname, \'/dist/client/index.html\'), (err, html) => {\n        if (err) {\n            throw err\n        }\n\n        html = html.toString().replace(\'SSR_APP_CONTENT\', `${appContent}`)\n        res.setHeader(\'Content-Type\', \'text/html\')\n        res.send(html)\n    })\n})\n\nserver.listen(80)\n
Run Code Online (Sandbox Code Playgroud)\n

我的设置

\n
- macOS Catalina 10.15.5\n- Nodejs v12.19.0\n- Google Chrome v86.0.4240.111\n
Run Code Online (Sandbox Code Playgroud)\n

Mar*_*nik 2

我终于发现这cache-loader导致了我的问题。
通过运行yarn build:server && yarn build:client,客户端构建使用服务器构建中的缓存组件,然后就没有render函数了,因为 ssr 构建ssrRender仅生成函数。我已经通过禁用中的函数的
缓存加载器来修复它。chainWebpackvue.config.js

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')
    ...
}
Run Code Online (Sandbox Code Playgroud)

参考: https: //forum.vuejs.org/t/disable-cache-loader-in-webpack-4-vue-cli-3/57561