【问题标题】:Angular Universal pre-generated pages: The selector "app-root" did not match any elementsAngular Universal 预生成页面:选择器“app-root”不匹配任何元素
【发布时间】:2018-03-28 21:03:40
【问题描述】:

我一直在尝试拼凑如何预先生成现有 Angular Universal 应用程序的页面。我被这个例外困住了:

选择器“app-root”没有匹配任何元素。

我在执行node dist/pre-render.js / 时遇到该异常。

请注意,此应用程序适用于 ng servenpm run build:universal; npm run serve:universal。所以这不仅仅是忘记在app.moduledeclarations 中放一些东西那么简单。

重现步骤:

  1. 运行npm run build:universal
  2. 运行node dist/pre-render.js /
  3. 观察错误The selector "app-root" did not match any elements

设置如下:

package.json脚本部分

我认为这与the Angular guide 相同。

"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

这也是straight out of the guide,除了你会注意到我还有一个额外的entry,它会从pre-render.ts 生成我的pre-render.js 脚本,我接下来会展示它。

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

我从the guide's server.ts filea book by Philippe Martin 那里得到了提示。指南还没有解释如何做这些事情,菲利普书中的例子对我不起作用。

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 的提供者

很明显,属性对某事来说一定很重要。

另外,我注意到在urlextraProviders 旁边放入document 属性没有任何效果,即使document 属性是一个没有任何&lt;app-root&gt; 元素的字符串。

【问题讨论】:

标签: angular angular-universal


【解决方案1】:

缺少的只是一个document 属性,该属性在字符串中的某处包含一个&lt;app-root&gt;&lt;/app-root&gt; 元素。

当我将此对象作为第二个参数传递给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 在its prerender.ts file 中所做的操作:

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

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

注意他们如何读取已编译的browser/index.html 文件的内容,并使用that 字符串作为document 属性。

【讨论】:

【解决方案2】:

在您的 server.ts 文件中添加以下行。

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

这将有助于在服务器端映射模块。

【讨论】:

  • 在我的情况下,我没有使用 Express 来渲染东西,而是将渲染的输出直接打印到控制台。所以ngExpressEngine 不适用。另外,我的server.ts 文件已经正确设置并正常工作。就我而言,我问的是我的自定义 pre-render.ts 文件。
  • 再详细一点:您所说的是服务器端渲染。此处概述了这与预渲染之间的区别:github.com/angular/…
  • 感谢您尝试回答。欢迎来到 Stack Overflow!
猜你喜欢
  • 1970-01-01
  • 2016-06-09
  • 2020-05-26
  • 1970-01-01
  • 1970-01-01
  • 2016-06-09
  • 1970-01-01
  • 2016-11-24
  • 2018-09-23
相关资源
最近更新 更多