Предварительно сгенерированные страницы Angular Universal: селектор app-root не соответствует ни одному элементу

Я пытался собрать воедино, как предварительно сгенерировать страницы существующего приложения Angular Universal. Я застрял на этом исключении:

Селектор app-root не соответствует ни одному элементу.

Я получаю это исключение, когда выполняю node dist/pre-render.js /.

Обратите внимание, что это приложение отлично работает с ng serve и npm run build:universal; npm run serve:universal. Так что это не так просто, как забыть вставить что-то в app.module declarations.

Действия по воспроизведению:

  1. Беги npm run build:universal
  2. Запустите node dist/pre-render.js /
  3. Обратите внимание на ошибку The selector "app-root" did not match any elements

Вот установка:

package.json раздел скриптов

Я думаю, что это идентично руководству по Angular.

"build:universal": "npm run build:client-and-server-bundles && npm run webpack:server",
"serve:universal": "node dist/server.js",
"build:client-and-server-bundles": "ng build --prod && ng build --prod --app 1 --output-hashing=false",
"webpack:server": "webpack --config webpack.server.config.js --progress --colors"

webpack.server.config.js

Это также прямо из руководства, за исключением того, что вы заметили, что у меня дополнительный entry, который создает мой pre-render.js сценарий из pre-render.ts, который я покажу дальше.

const path = require('path');
const webpack = require('webpack');


module.exports = {
    entry: {
        server: './server.ts',
        'pre-render': './pre-render.ts'
    },
    resolve: { extensions: ['.js', '.ts'] },
    target: 'node',
    // This makes sure we include node_modules and other third-party libraries.
    externals: [/(node_modules|main\..*\.js)/],
    output: {
        path: path.join(__dirname, 'dist'),
        filename: '[name].js'
    },
    module: {
        rules: [{
            test: /\.ts$/,
            loader: 'ts-loader'
        }]
    },
    plugins: [
        // Temporary Fix for issue: https://github.com/angular/angular/issues/11580
        // for 'WARNING Critical dependency: the request of a dependency is an expression'
        new webpack.ContextReplacementPlugin(
            /(.+)?angular(\\|\/)core(.+)?/,
            path.join(__dirname, 'src'), // location of your src
            {} // a map of your routes
        ),
        new webpack.ContextReplacementPlugin(
            /(.+)?express(\\|\/)(.+)?/,
            path.join(__dirname, 'src'),
            {}
        )
    ]
};

pre-render.ts

Я взял подсказки из server.ts файла руководства и из книга Филиппа Мартина. В руководстве пока не объясняется, как это сделать, а пример из книги Филиппа мне не подошел.

import 'zone.js/dist/zone-node';
import { renderModuleFactory } from '@angular/platform-server';
import { enableProdMode } from '@angular/core';
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';

const args = process.argv.slice(2);
if (args.length !== 1) {
    process.stdout.write('Usage: node dist/pre-render.js <url>');
    process.exit();
}

enableProdMode();
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main.bundle');

renderModuleFactory(
    AppServerModuleNgFactory,
    {
        url: args[0],
        extraProviders: [
            provideModuleMap(LAZY_MODULE_MAP)
        ]
    }
).then(rendered => {
    process.stdout.write(rendered);
});

Честно говоря, я понятия не имею, для чего это свойство extraProviders и нужно ли туда бросать provideModuleMap(...). Но если я не дам ему ничего extraProviders, я получаю такую ​​ошибку:

NullInjectorError: нет поставщика для InjectionToken MODULE_MAP

Так ясно, что собственность должна быть для чего-то важна.

Кроме того, я заметил, что добавление свойства document рядом с url и extraProviders не имеет никакого эффекта, даже если свойство document является строкой без каких-либо элементов <app-root>.


person Matt Thomas    schedule 27.03.2018    source источник
comment
С помощью сотрудников gitter.im/angular/angular мне может не хватать только правильного содержимого для поместить в собственность document. Они также указали мне на github.com/angular/universal-starter, с которым я буду сравнивать мой код. Я должен узнать больше завтра.   -  person Matt Thomas    schedule 28.03.2018


Ответы (2)


Не хватало только свойства document, содержащего где-то в строке элемент <app-root></app-root>.

Это работает, когда я передаю этот объект в качестве второго параметра renderModuleFactory. Обратите внимание на добавление свойства document:

document: `<!DOCTYPE html>
<html>
<body>
<app-root></app-root>
</body>
</html>`,
url: args[0],
extraProviders: [
    provideModuleMap(LAZY_MODULE_MAP)
]

Теперь, хотя это приводит к визуализации чего-либо, это не совсем то, потому что мы начинаем со строки document, которая отличается от содержимого нашего index.html.

Чтобы исправить это, просто сделайте то, что делает Angular Universal Starter в его prerender.ts файле. :

const index = readFileSync(join('browser', 'index.html'), 'utf8');

renderModuleFactory(AppServerModuleNgFactory, {
  document: index,
  url: route,
  extraProviders: [
    provideModuleMap(LAZY_MODULE_MAP)
  ]
})

Обратите внимание, как они читают содержимое скомпилированного файла browser/index.html, и используют эту строку для свойства document.

person Matt Thomas    schedule 28.03.2018
comment
Замечу, что это теперь делает мой pre-render.ts скрипт похожим на Angular Universal Стартовый pre-render.ts скрипт, только хуже. Так что я мог бы просто использовать их. - person Matt Thomas; 28.03.2018

Добавьте следующие строки в свой файл server.ts.

import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
app.engine('html', ngExpressEngine({
  bootstrap: AppServerModuleNgFactory,
  providers: [
    provideModuleMap(LAZY_MODULE_MAP)
  ]
 }));

Это поможет отобразить модуль на стороне сервера.

person KrGyan    schedule 28.03.2018
comment
В моем случае я не использую Express для рендеринга, а печатаю рендеринг прямо на консоль. Итак, ngExpressEngine не применяется. Кроме того, мой server.ts файл уже настроен и работает правильно. В моем случае я спрашиваю о моем собственном pre-render.ts файле. - person Matt Thomas; 28.03.2018
comment
Чтобы уточнить немного больше: то, о чем вы говорите, - это рендеринг на стороне сервера. Разница между этим и предварительной визуализацией описана здесь: github.com/angular/ - person Matt Thomas; 28.03.2018
comment
Тем не менее, спасибо за попытку ответа. И добро пожаловать в Stack Overflow! - person Matt Thomas; 28.03.2018