gpt4 book ai didi

Angular 7 SSR - NgZone 的问题

转载 作者:太空狗 更新时间:2023-10-29 17:07:59 27 4
gpt4 key购买 nike

我最近将我公司的网站从 React 移到了 Angular,因为我们的大多数项目已经在 Angular 7 上。作为“使用最新和最伟大”的人,我决定实现服务器端渲染使谷歌页面速度评级接近 100/100(目前为 42/100)。我在本周的大部分时间里一直在修补它,但没有成功 - 最近的障碍对我来说特别难以克服。这是有关我的设置的简要信息,然后我将详细介绍:

  • NodeJS 8.9.1
  • Angular 7 最新
  • Webpack 4.26.0
  • @ngtools/webpack 7.0.5
  • 不使用 angular-cli
  • AoT 设置
  • 单页应用

  • 这是我在尝试呈现为 SSR 设置的 layout.html 文件时遇到的错误:
    TypeError: Cannot read property 'subscribe' of undefined
    at new ApplicationRef (C:\code\lemmsoftWebsite\repo\public\site\serverBuild\main.js:43263:37)
    at _createClass (C:\code\lemmsoftWebsite\repo\public\site\serverBuild\main.js:46296:20)
    at _createProviderInstance (C:\code\lemmsoftWebsite\repo\public\site\serverBuild\main.js:46258:26)
    at initNgModule (C:\code\lemmsoftWebsite\repo\public\site\serverBuild\main.js:46190:32)
    at new NgModuleRef_ (C:\code\lemmsoftWebsite\repo\public\site\serverBuild\main.js:46918:9)
    at createNgModuleRef (C:\code\lemmsoftWebsite\repo\public\site\serverBuild\main.js:46907:12)
    at Object.debugCreateNgModuleRef [as createNgModuleRef] (C:\code\lemmsoftWebsite\repo\public\site\serverBuild\main.js:48738:12)
    at NgModuleFactory_.module.exports.NgModuleFactory_.create (C:\code\lemmsoftWebsite\repo\public\site\serverBuild\main.js:49466:25)
    at C:\code\lemmsoftWebsite\repo\node_modules\@angular\core\bundles\core.umd.js:14656:47
    at ZoneDelegate.module.exports.ZoneDelegate.invoke (C:\code\lemmsoftWebsite\repo\public\site\serverBuild\main.js:139510:26)
    at Object.onInvoke (C:\code\lemmsoftWebsite\repo\node_modules\@angular\core\bundles\core.umd.js:14194:37)
    at ZoneDelegate.module.exports.ZoneDelegate.invoke (C:\code\lemmsoftWebsite\repo\public\site\serverBuild\main.js:139509:32)
    at Zone.module.exports.Zone.run (C:\code\lemmsoftWebsite\repo\public\site\serverBuild\main.js:139260:43)
    at NgZone.run (C:\code\lemmsoftWebsite\repo\node_modules\@angular\core\bundles\core.umd.js:14108:32)
    at PlatformRef.bootstrapModuleFactory (C:\code\lemmsoftWebsite\repo\node_modules\@angular\core\bundles\core.umd.js:14654:27)
    at renderModuleFactory (C:\code\lemmsoftWebsite\repo\node_modules\@angular\platform-server\bundles\platform-server.umd.js:1033:43)
    at View.module.app.engine (C:\code\lemmsoftWebsite\repo\modules\clients\site\layout\index.js:60:4)
    at View.render (C:\code\lemmsoftWebsite\repo\node_modules\express\lib\view.js:135:8)
    at tryRender (C:\code\lemmsoftWebsite\repo\node_modules\express\lib\application.js:640:10)
    at Function.render (C:\code\lemmsoftWebsite\repo\node_modules\express\lib\application.js:592:3)
    at ServerResponse.render (C:\code\lemmsoftWebsite\repo\node_modules\express\lib\response.js:1008:7)
    at C:\code\lemmsoftWebsite\repo\modules\clients\site\layout\index.js:83:9

    在仔细阅读 main.js 包文件后,我将问题定位为以下几点:

    var ApplicationRef = /** @class */ (function () {
    /** @internal */
    function ApplicationRef(_zone, _console, _injector, _exceptionHandler, _componentFactoryResolver, _initStatus) {
    var _this = this;
    this._zone = _zone; // in this method, the _zone argument is {}, so there is no onMicrotaskEmpty method in it => when this._zone.onMicrotaskEmpty.subscribe() is attempted, we get "Cannot read property 'subscribe' of undefined"
    this._console = _console;
    this._injector = _injector;
    this._exceptionHandler = _exceptionHandler;
    this._componentFactoryResolver = _componentFactoryResolver;
    this._initStatus = _initStatus;
    this._bootstrapListeners = [];
    this._views = [];
    this._runningTick = false;
    this._enforceNoNewChanges = false;
    this._stable = true;
    // more code
    }
    // more code
    }


    在此方法中,_zone 参数是 {},因此其中没有 onMicrotaskEmpty 方法 => 当尝试 this._zone.onMicrotaskEmpty.subscribe() 时,我们得到“无法读取未定义的属性‘订阅’”。我继续挖掘堆栈跟踪 - 这是上一步,调用 new ApplicationRef 并将 _zone 作为 {} 传递:

    function _createClass(ngModule, ctor, deps) {
    var len = deps.length;
    switch (len) {
    case 0:
    return new ctor();
    case 1:
    return new ctor(resolveNgModuleDep(ngModule, deps[0]));
    case 2:
    return new ctor(resolveNgModuleDep(ngModule, deps[0]), resolveNgModuleDep(ngModule, deps[1]));
    case 3:
    return new ctor(resolveNgModuleDep(ngModule, deps[0]), resolveNgModuleDep(ngModule, deps[1]), resolveNgModuleDep(ngModule, deps[2]));
    default:
    // this is where we get some insight into the cause of the error
    var depValues = new Array(len);
    for (var i = 0; i < len; i++) {
    depValues[i] = resolveNgModuleDep(ngModule, deps[i]);
    }
    // if we do console.log(deps[0], depValues[0]), which is _zone, we get interesting stuff...
    return new (ctor.bind.apply(ctor, Object(tslib__WEBPACK_IMPORTED_MODULE_0__["__spread"])([void 0], depValues)))();
    }
    }


    这是我们深入了解错误原因的地方 - 在开关的“默认” block 中。如果我们在 for 循环(也就是 _zone)之后使用 console.log(deps[0], depValues[0]),我们会得到一些有趣的东西:

    // deps[0]
    { flags: 0,
    token:
    { [Function: NgZone]
    isInAngularZone: [Function],
    assertInAngularZone: [Function],
    assertNotInAngularZone: [Function] },
    tokenKey: 'NgZone_29' }
    // depValues[0]
    {}


    所以,这就是罪魁祸首,我想! 'resolveNgModuleDep' 搞砸了!所以我继续挖掘:

    function resolveNgModuleDep(data, depDef, notFoundValue) {
    if (notFoundValue === void 0) { notFoundValue = Injector.THROW_IF_NOT_FOUND; }
    var former = setCurrentInjector(data);
    try {
    if (depDef.flags & 8 /* Value */) {
    return depDef.token;
    }
    if (depDef.flags & 2 /* Optional */) {
    notFoundValue = null;
    }
    if (depDef.flags & 1 /* SkipSelf */) {
    return data._parent.get(depDef.token, notFoundValue);
    }
    var tokenKey_1 = depDef.tokenKey;
    switch (tokenKey_1) {
    case InjectorRefTokenKey:
    case INJECTORRefTokenKey:
    case NgModuleRefTokenKey:
    return data;
    }
    var providerDef = data._def.providersByKey[tokenKey_1];
    var injectableDef = void 0;
    if (providerDef) {
    var providerInstance = data._providers[providerDef.index];
    if (providerInstance === undefined) {
    providerInstance = data._providers[providerDef.index] =
    _createProviderInstance(data, providerDef);
    }
    return providerInstance === UNDEFINED_VALUE ? undefined : providerInstance;
    }
    else if ((injectableDef = getInjectableDef(depDef.token)) && targetsModule(data, injectableDef)) {
    var index = data._providers.length;
    data._def.providersByKey[depDef.tokenKey] = {
    flags: 1024 /* TypeFactoryProvider */ | 4096 /* LazyProvider */,
    value: injectableDef.factory,
    deps: [], index: index,
    token: depDef.token,
    };
    data._providers[index] = UNDEFINED_VALUE;
    return (data._providers[index] =
    _createProviderInstance(data, data._def.providersByKey[depDef.tokenKey]));
    }
    else if (depDef.flags & 4 /* Self */) {
    return notFoundValue;
    }
    // there it is!
    return data._parent.get(depDef.token, notFoundValue);
    }
    finally {
    setCurrentInjector(former);
    }
    }


    就在那里!就在 finally 之前,在返回 data._parent.get(depDef.token, notFoundValue) 的那一行 - 这是传递 depDef.token(在我们的例子中是 NgZone)的地方,notFoundValue 是 null。返回的对象只是 {},因此所有的麻烦都在后面。
    这是我设法得到的,我一直在来回试图从这里解决它,但无济于事。相信我,我已经在 stackoverflow 和谷歌中搜索了一遍又一遍;我已经阅读了 1000 篇中等帖子 - 没有成功。我不使用 angular-cli 是因为我喜欢自定义我的 webpack 配置等,但我怀疑这就是原因,因为 angular-cli 本身在引擎盖下使用 webpack。我将在下面的几个片段中粘贴一些其他内容 - 我的 webpack 配置、呈现 html 和 angular 包的服务器方法等。

    // the webpack config

    'use strict'

    const
    AngularCompilerPlugin = require( "@ngtools/webpack" ).AngularCompilerPlugin,
    BellOnBundlerErrorPlugin = require('bell-on-bundler-error-plugin'),
    BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin,
    path = require('path'),
    ProgressBarPlugin = require('progress-bar-webpack-plugin'),
    UglifyJsPlugin = require('uglifyjs-webpack-plugin'),
    webpack = require('webpack')


    module.exports = (config, name) => {
    let includePath = config.clientPath,
    publicPath = path.join(config.publicPath, 'serverBuild'),
    libPath = path.join(__dirname, '../../lib'),
    nodeModulesPath = config.nodeModulesPath,
    include = [includePath]

    return {
    target: 'node',
    mode: 'none',
    entry: [
    path.join(includePath, 'polyfills.ts'),
    path.join(includePath, 'vendor.common.ts'),
    path.join(includePath, 'vendor.server.ts'),
    path.join(includePath, 'index.server.ts')
    ],
    output: {
    path: publicPath,
    filename: '[name].js',
    chunkFilename: '[id].chunk.js',
    publicPath: '/dist/',
    libraryTarget: 'commonjs-module'
    },
    resolve: {
    extensions: ['.ts', '.js'],
    modules: ['node_modules', libPath]
    },
    module: {
    rules: [
    {
    test: /\.pug$/,
    include: [libPath, includePath],
    use: ['raw-loader', 'pug-html-loader']
    },
    {
    test: /\.css$/,
    include: [libPath, nodeModulesPath, includePath],
    exclude: [],
    use: ['to-string-loader', 'css-loader']
    },
    {
    test: /\.less$/,
    exclude: [],
    use: ['to-string-loader', 'css-loader', 'less-loader']
    },
    {
    test: /\.scss$/,
    include: [libPath, nodeModulesPath, includePath],
    exclude: [],
    use: ['raw-loader', 'sass-loader']
    },
    {
    test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
    include: [includePath, libPath],
    use: [{
    loader: '@ngtools/webpack'
    }],
    exclude: [/\.(spec|e2e)\.ts$/]
    },
    {
    test: /\.json$/,
    include,
    exclude: [],
    use: ["json2-loader"]
    }
    ]
    },
    stats: 'verbose',
    plugins: [
    // new webpack.HotModuleReplacementPlugin(),
    new BellOnBundlerErrorPlugin(),
    new ProgressBarPlugin({
    format: ' build [:bar] (:percent) - (:elapsed seconds)',
    clear: false,
    complete: '#',
    summary: 'true'
    }),
    // new webpack.NamedModulesPlugin(),
    new AngularCompilerPlugin({
    tsConfigPath: path.join(__dirname, '../../tsconfig.server.json'),
    entryModule: path.join(includePath, 'app.server.ts#AppServerModule'),
    sourceMap: true
    })
    ]
    }
    }



    // tsconfig.json and tsconfig.server.json
    {
    "compilerOptions": {
    "baseUrl": ".",
    "target": "es6",
    "module": "es2015",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "importHelpers": true,
    "strictNullChecks": false,
    "lib": [
    "es2015",
    "dom"
    ],
    "typeRoots": [
    "node_modules/@types",
    "typings"
    ],
    "types": [
    "hammerjs",
    "node"
    ],
    "paths": {
    "ramster-ui/*": ["lib/ramster-ui/*"]
    }
    },
    "include": [
    "clients/**/*",
    "lib/ramster-ui/**/*"
    ],
    "exclude": [
    "clients/**/*.spec.ts",
    "clients/**/*.e2e.ts"
    ],
    "awesomeTypescriptLoaderOptions": {
    "forkChecker": true,
    "useWebpackText": true
    },
    "angularCompilerOptions": {
    "genDir": "./compiled",
    "skipMetadataEmit": true
    },
    "compileOnSave": false,
    "buildOnSave": false,
    "atom": {
    "rewriteTsconfig": false
    }
    }


    {
    "extends": "./tsconfig.json",
    "include": [
    "clients/**/polyfills.ts",
    "clients/**/vendor.common.ts",
    "clients/**/vendor.server.ts",
    "clients/**/app.server.ts",
    "clients/**/index.server.ts",
    ],
    "exclude": [
    "clients/**/index.ts",
    "clients/**/vendor.browser.ts",
    "clients/**/app.ts",
    "clients/**/*.spec.ts",
    "clients/**/*.e2e.ts"
    ],
    "awesomeTypescriptLoaderOptions": {
    "forkChecker": true,
    "useWebpackText": true
    },
    "angularCompilerOptions": {
    "genDir": "./compiled",
    "skipMetadataEmit": true
    },
    "compileOnSave": false,
    "buildOnSave": false,
    "atom": {
    "rewriteTsconfig": false
    }
    }



    // excerpts from my server setup

    // this method is called before the server is started
    setup() {
    const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require(path.join(__dirname, '../../../../public/site/serverBuild', 'main'))
    // LAZY_MODULE_MAP is undefined for now
    this.module.app.engine('html', (_, options, callback) => {
    renderModuleFactory(
    AppServerModuleNgFactory, {}
    ).then((html) => callback(null, html), (error) => callback(error))
    })
    this.module.app.set('view engine', 'html')
    }

    // the method returned by "loadLayout" is mounted in expressjs
    loadLayout() {
    const {module} = this
    return function* (req, res, next) {
    try {
    res.render(path.join('../../../../public/site/layout.html'), {req, res})
    } catch (e) {
    req.locals.error = e
    next()
    }
    }
    }



    // polyfills.ts
    import 'core-js/es6'
    import 'reflect-metadata'


    // vendor.common.ts
    import 'rxjs/add/operator/first'
    import 'rxjs/add/operator/toPromise'
    import 'popper.js'

    import '@angular/common'
    import '@angular/core'
    import '@angular/flex-layout'
    import '@angular/forms'
    import '@angular/material'
    import '@angular/router'


    // vendor.server.ts
    import 'zone.js/dist/zone-node'
    import '@angular/platform-server'


    // index.server.ts
    import {enableProdMode} from '@angular/core'
    if (process.env.NODE_ENV === 'production') {
    enableProdMode()
    }
    export * from './app.server.ngfactory'


    // app.server.ts
    import {NgModule} from '@angular/core'
    import {ServerModule} from '@angular/platform-server'

    @NgModule({
    imports: [
    ServerModule
    ],
    exports: [],
    declarations: [],
    providers: [],
    bootstrap: []
    })
    class AppServerModule {}

    export {AppServerModule}


    您会看到我已经将服务器端应用程序精简到非常基础,因此我可以消除由我编写的内容引起的错误。
    任何帮助将非常感激。

    最佳答案

    ZoneJS 作为 AMD 模块不能正常工作。您正在使用 SystemJS AMD extra 并且 zone.js 捆绑为 UMD 格式——这导致 Zone 被用作触发损坏行为的 AMD 模块

    zone.js 上的完整解释和问题 - https://github.com/angular/angular/issues/36827

    问题的最小演示 - https://codesandbox.io/s/quirky-antonelli-2kusz?file=/index.html

    解决方法 - https://codesandbox.io/s/sweet-shape-18x7b

    关于Angular 7 SSR - NgZone 的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53466406/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com