VueJs: ошибка с отображением на стороне сервера и Typescript

Я пытаюсь создать стек с помощью Server Side Rendering (SSR) и Typescript. Все кажется прекрасным, но я получил ошибку: TypeError: Cannot read property 'render' of undefined. Вот полная трассировка стека:

TypeError: Cannot read property 'render' of undefined
    at normalizeRender (/Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/vue-server-renderer/build.js:6621:19)
    at render (/Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/vue-server-renderer/build.js:6840:5)
    at Object.renderToString (/Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/vue-server-renderer/build.js:6871:9)
    at /Users/shoun/Documents/repositories/vuejs-ssr-typescript/dist/server.js:16:14
    at Layer.handle [as handle_request] (/Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/express/lib/router/layer.js:95:5)
    at next (/Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/express/lib/router/route.js:137:13)
    at Route.dispatch (/Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/express/lib/router/route.js:112:3)
    at Layer.handle [as handle_request] (/Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/express/lib/router/layer.js:95:5)
    at /Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/express/lib/router/index.js:281:22
    at param (/Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/express/lib/router/index.js:354:14)
TypeError: Cannot read property 'render' of undefined
    at normalizeRender (/Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/vue-server-renderer/build.js:6621:19)
    at render (/Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/vue-server-renderer/build.js:6840:5)
    at Object.renderToString (/Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/vue-server-renderer/build.js:6871:9)
    at /Users/shoun/Documents/repositories/vuejs-ssr-typescript/dist/server.js:16:14
    at Layer.handle [as handle_request] (/Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/express/lib/router/layer.js:95:5)
    at next (/Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/express/lib/router/route.js:137:13)
    at Route.dispatch (/Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/express/lib/router/route.js:112:3)
    at Layer.handle [as handle_request] (/Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/express/lib/router/layer.js:95:5)
    at /Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/express/lib/router/index.js:281:22
    at param (/Users/shoun/Documents/repositories/vuejs-ssr-typescript/node_modules/express/lib/router/index.js:354:14)

Вот моя конфигурация сервера:

import * as express from 'express';
import * as path from 'path';
import * as VueRender from 'vue-server-renderer';
import * as fs from 'fs-extra';
import app from './assets/app';
declare var __dirname;

// Get the HTML layout
const layout = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8');

// Create a renderer
const renderer = VueRender.createRenderer();

let server = express();

server.get('/hello', function (req, res) {
    res.send('Hello World!');
});

server.use('/assets', express.static(path.join(__dirname, 'assets')));

// Handle all GET requests
server.get('*', function (request, response) {
    // Render our Vue app to a string
    renderer.renderToString(
        // Create an app instance
        app(),
        // Handle the rendered result
        function (error, html) {
            // If an error occurred while rendering...
            if (error) {
                // Log the error in the console
                console.error(error);
                // Tell the client something went wrong
                return response
                    .status(500)
                    .send('Server Error')
            }
            // Send the layout with the rendered app HTML
            response.send(layout.replace('<div id="app"></div>', html))
        }
    )
});

let port = 4500;
server.listen(port, () => {
    console.log(`App listening on ${port}`);
});

Вы можете найти исходный код в моем репозитории github: https://github.com/sichida/vuejs-ssr-typescript. Я мог бы использовать некоторую помощь, потому что я застрял...

Большое спасибо!

Ответ 1

Я проверил ваше репо и проблема на самом деле находится в файле src/assets/app.ts, в функции createApp вы возвращаете объект типа ComponentOptions, но renderToString берет объект типа Vue.

На самом деле это может быть намного проще, чем у вас сейчас:

import * as Vue from 'vue';

let createApp = function () {
  return new Vue({
    props: ['message'],
    template: '<span>{{ message }}</span>',
  });
};
export default createApp;

И для этого вам нужно только вернуть новый экземпляр Vue.

Ответ 2

Что я обнаружил, что для того, чтобы Vue что-то отображал, ему нужно одно:

  • render свойство
  • template свойство
  • Быть $mount() ed

Теперь, если вы похожи на меня, и у вас было что-то подобное в вашем HTML:

<div id="app">
    <navbar></navbar>
    ...
</div>

и используется для его монтирования как app.$mount('#app'), , что вам нужно сделать:

  • Переместите <div id="app"> полностью с его содержимым в компонент (возможно, назовите его App.vue?)
  • Добавить свойство render в экземпляр Vue, например render: h => h(App), где App - только что созданный компонент
  • Используйте этот компонент непосредственно для SSR, т.е. верните его из своего entry-server.js или другого
  • Сделайте $mount этот экземпляр в entry-client.js для гидратации