angular 动态懒加载组件在使用esbuild时无法进行水分处理,

yduiuuwa  于 4个月前  发布在  Angular
关注(0)|答案(8)|浏览(47)

命令

build

是否为回归问题?

  • 是的,这种行为在之前的版本中是有效的

在之前没有此问题的版本中使用的是

Webpack

描述

在使用esbuild时,hydration无法检测到动态渲染的组件,这在webpack中是可以正常工作的。

最小复现

这里有一个包含工作和损坏版本的最小复现仓库:
损坏(Esbuild) https://github.com/koenig-dominik/ng-hydration-regression/tree/ng17-broken
工作(Webpack) https://github.com/koenig-dominik/ng-hydration-regression/tree/ng17-working
相关文件: https://github.com/koenig-dominik/ng-hydration-regression/blob/ng17-broken/src/app/dynamic.directive.ts
只需执行 npm run start 并查看控制台输出中的不同组件的水分化情况。

异常或错误

  • 无响应*

您的环境

Angular CLI: 17.0.3
Node: 18.17.1
Package Manager: npm 10.2.3
OS: win32 x64

Angular: 17.0.4
... animations, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, platform-server
... router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1700.3 (cli-only)
@angular-devkit/build-angular   17.0.3
@angular-devkit/core            17.0.3 (cli-only)
@angular-devkit/schematics      17.0.3
@angular/cli                    17.0.3
@angular/ssr                    17.0.3
@schematics/angular             17.0.3
rxjs                            7.8.1
typescript                      5.2.2
zone.js                         0.14.2

是否有其他相关信息?

  • 无响应*
1mrurvl1

1mrurvl11#

在生成的代码中,我们可以看到:

@angular-devkit/build-angular:browser

Angular 渲染了2个组件和6个节点,其中0个组件被跳过。
index.html

<!DOCTYPE html><html lang="en" data-critters-container><head>
  <meta charset="utf-8">
  <title>NgHydrationRegression</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
<style>/*!**********************************************************************************************************************************************************************************************************************!*\
  !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[5].rules[0].oneOf[0].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[5].rules[0].oneOf[0].use[2]!./src/styles.css?ngGlobalStyle ***!
  \**********************************************************************************************************************************************************************************************************************/
/* You can add global styles to this file, and also import other style files */

/*# sourceMappingURL=styles.css.map*/</style><link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles.css"></noscript><style ng-app-id="ng">[_nghost-ng-c3597624338] {
  display: block;
}

/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8uL3NyYy9hcHAvZHluYW1pYy9keW5hbWljLmNvbXBvbmVudC5jc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7RUFDRSxjQUFjO0FBQ2hCIiwic291cmNlc0NvbnRlbnQiOlsiOmhvc3Qge1xuICBkaXNwbGF5OiBibG9jaztcbn1cbiJdLCJzb3VyY2VSb290IjoiIn0= */</style></head>
<body><script id="__bs_script__">//<![CDATA[
  (function() {
    try {
      var script = document.createElement('script');
      if ('async') {
        script.async = true;
      }
      script.src = '/browser-sync/browser-sync-client.js?v=2.29.3'.replace("HOST", location.hostname);
      if (document.body) {
        document.body.appendChild(script);
      } else if (document.head) {
        document.head.appendChild(script);
      }
    } catch (e) {
      console.error("Browsersync: could not append script tag", e);
    }
  })()
//]]></script>
<!--nghm-->
  <app-root ng-version="17.0.4" ngh="1" ng-server-context="ssr"><app-dynamic _nghost-ng-c3597624338 ngh="0"><p _ngcontent-ng-c3597624338>dynamic works!</p></app-dynamic><!--container--></app-root>
<script src="runtime.js" type="module"></script><script src="polyfills.js" type="module"></script><script src="vendor.js" type="module"></script><script src="main.js" type="module"></script>

<script id="ng-state" type="application/json">{"__nghData__":[{},{"t":{"0":"t0"},"c":{"0":[{"i":"c3597624338","r":1}]}}]}</script></body></html>

@angular-devkit/build-angular:application

Angular 渲染了1个组件和3个节点,其中0个组件被跳过。
index.html

<!DOCTYPE html><html lang="en"><head>
  <script type="module" src="/@vite/client"></script>

  <meta charset="utf-8">
  <title>NgHydrationRegression</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.css"></head>
<body><!--nghm-->
  <app-root ng-version="17.0.4" ngh="0" ng-server-context="ssr"><!--container--></app-root>
<script src="polyfills.js" type="module"></script><script src="main.js" type="module"></script>

<script id="ng-state" type="application/json">{"__nghData__":[{"t":{"0":"t0"},"c":{"0":[]}}]}</script></body></html>

请注意,这两个结果在浏览器中显示相同的结果,但应用程序构建器在发送响应之前不会等待动态组件被渲染。我怀疑这是由于应用程序被认为是稳定的时候不同的原因。

798qvoo8

798qvoo82#

老实说,我甚至没有在我的最小复制品中看到服务器没有输出动态组件。在我更复杂的生产应用中,这确实有效。
我已经更新了仓库以重现此状态:https://github.com/koenig-dominik/ng-hydration-regression/tree/cf741f658cef1d62d2813c3e3c5a37be13b8b010

@angular-devkit/build-angular:application

index.html

<!DOCTYPE html><html lang="en"><head>
  <script type="module" src="/@vite/client"></script>

  <meta charset="utf-8">
  <title>NgHydrationRegression</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.css"><style ng-app-id="ng">

[_nghost-ng-c3597624338] {
  display: block;
}
/*# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsic3JjL2FwcC9keW5hbWljL2R5bmFtaWMuY29tcG9uZW50LmNzcyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiOmhvc3Qge1xuICBkaXNwbGF5OiBibG9jaztcbn1cbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBQTtBQUNFLFdBQVM7QUFDWDsiLAogICJuYW1lcyI6IFtdCn0K */</style></head>
<body><!--nghm-->
  <app-root ng-version="17.0.4" ngh="1" ng-server-context="ssr"><app-dynamic _nghost-ng-c3597624338="" ngh="0"><p _ngcontent-ng-c3597624338="">dynamic works!</p></app-dynamic><!--container--></app-root>
<script src="polyfills.js" type="module"></script><script src="main.js" type="module"></script>

<script id="ng-state" type="application/json">{"__nghData__":[{},{"t":{"0":"t1"},"c":{"0":[{"i":"c3597624338","r":1}]}}]}</script></body></html>

尽管它确实在服务器生成的代码中输出了<app-dynamic>,但hydration仍然无法正常工作。
但这仍然可能是由于错误的稳定检测引起的。

7cwmlq89

7cwmlq893#

我今天对这个问题进行了更多的研究,我怀疑这是一个与Zone.js和promise补丁有关的问题。
以下是代码:

import { ApplicationRef, Component, inject } from '@angular/core';
import { tap } from 'rxjs';

@Component({
  selector: 'app-root',
  standalone: true,
  template: '',
})
export class AppComponent {
  constructor() {
    inject(ApplicationRef)
      .isStable.pipe(tap((isStable) => console.log('- isStable: ' + isStable)))
      .subscribe();
  }

  async ngOnInit() {
    console.log('ngOnInit');
    await Promise.resolve()
      .then(() => import('./dynamic-data'))
      .then(
        () =>
          new Promise<void>((resolve) => {
            setTimeout(() => {
              resolve();
            }, 5000);
          })
      )
      .then(() => console.log('- Promise resolved.'));
  }
}

应用程序将进入稳定和不稳定状态:

app.component.ts:12 - isStable: false
app.component.ts:17 ngOnInit
app.component.ts:12 - isStable: true
app.component.ts:12 - isStable: false
app.component.ts:28 - Promise resolved.
app.component.ts:12 - isStable: true

然而,如果我们从上面的代码中注解掉.then(() => import('./dynamic-data'))
在控制台中我们观察到:

app.component.ts:17 - isStable: false
app.component.ts:17 ngOnInit
app.component.ts:28 - Promise resolved.
app.component.ts:12 - isStable: true
lhcgjxsq

lhcgjxsq4#

我更新了我创建的仓库,使用了最新的Angular版本:https://github.com/koenig-dominik/ng-hydration-regression/tree/2494627fa5110b6ae39f068ceb1e361653eee409。在这里,我添加了所有我知道的创建动态组件的方法。我还添加了同时使用webpack和esbuild启动开发服务器的可能性,只需打开两个终端并执行npm run startnpm run start:webpack
我从中学到的是:

ngComponentOutlet

eager与webpack和esbuild一起工作
lazy仅与webpack一起工作,不与esbuild一起工作

ViewContainerRef.CreateComponent

eager与webpack和esbuild一起工作
lazy仅与webpack一起工作,不与esbuild一起工作

CreateComponent.attachView

eager与webpack和esbuild一起工作
lazy仅与webpack一起工作,不与esbuild一起工作

CreateComponent.insert

eager与webpack和esbuild不起作用
lazy与webpack和esbuild不起作用

nom7f22z

nom7f22z5#

这里的根本原因是 import 返回了一个无法被 Zone.js 补丁的原生 Promise。

4dbbbstv

4dbbbstv6#

这个有没有更新?

5f0d552i

5f0d552i7#

我更新了仓库到angular 18(并让它变得更清楚如何测试)但是结果仍然和之前一样:$x^{https://github.com/koenig-dominik/ng-hydration-regression/tree/44ffc155eb104c4f93a8154c0046a4a9da5f5530}$

wvt8vs2t

wvt8vs2t8#

我也遇到了这个问题。我尝试优化我们的Website页面构建器,并像上面的例子一样懒加载所有内容元素组件。组件如预期地被排除在主包之外,但水化失败,导致页面上出现大量的CLS。顺便说一下,我们正在运行Angular 18。

是否有办法手动告诉Angular关于动态添加的组件?

相关问题