You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular 16集成FirebaseUI与SSR时window未定义错误求助

Angular 16 SSR + FirebaseUI 解决 window is not defined(dialog-polyfill依赖问题)

问题描述

运行npm run dev:ssr时触发以下错误:

./node_modules/dialog-polyfill/dialog-polyfill.js:4
  var supportCustomEvent = window.CustomEvent;
                           ^
ReferenceError: window is not defined
    at <anonymous> (./node_modules/dialog-polyfill/dialog-polyfill.js:4:28)
    at Object.44252 (./node_modules/dialog-polyfill/dialog-polyfill.js:738:2)
    at __webpack_require__ (./webpack/bootstrap:19:1)
    at Module.73697 (./node_modules/express/node_modules/serve-static/index.js:210:1)
    at __webpack_require__ (./webpack/bootstrap:19:1)
    at Module.10388 (./src/app/components/home/home.component.html:46:10)
    at __webpack_require__ (./webpack/bootstrap:19:1)
    at Module.11838 (./server.ts:74:35)
    at __webpack_require__ (./webpack/bootstrap:19:1)
    at Module.50041 (./src/app/app.component.html:3:8)

dialog-polyfill是FirebaseUI的依赖,未在项目中直接使用。已尝试platformBrowser环境判断和domino模拟全局对象,但问题仍存在。

解决思路

1. 完善Server.ts的全局对象模拟

domino默认未完全模拟CustomEvent等浏览器API,且全局对象的设置时机需早于Angular代码导入:
修改server.ts,确保在引入任何Angular模块前完成全局对象模拟:

const domino = require('domino');
const fs = require('fs');
const path = require('path');
const join = path.join;

// 先模拟全局浏览器对象
const template = fs.readFileSync(join(process.cwd(), 'dist/<PROJECTNAME>/browser', 'index.html')).toString();
const window = domino.createWindow(template);
global['window'] = window;
global['document'] = window.document;
// 补充模拟CustomEvent(错误直接触发点)
global['CustomEvent'] = window.CustomEvent || function(event, params) {
  params = params || { bubbles: false, cancelable: false, detail: null };
  const evt = document.createEvent('CustomEvent');
  evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
  return evt;
};
// 补充其他可能需要的DOM对象
global['HTMLElement'] = window.HTMLElement;
global['Node'] = window.Node;

// 之后再引入Angular相关模块
import { APP_BASE_HREF } from '@angular/common';
import { ngExpressEngine } from '@nguniversal/express-engine';
// ... 保留原有其他import和server启动代码

2. 配置Webpack排除服务端打包dialog-polyfill

dialog-polyfill是纯浏览器端polyfill,服务端无需打包,在angular.json的server构建配置中添加externals:

"projects": {
  "<PROJECTNAME>": {
    "architect": {
      "server": {
        "builder": "@angular-devkit/build-angular:server",
        "options": {
          "outputPath": "dist/<PROJECTNAME>/server",
          "main": "server.ts",
          "tsConfig": "tsconfig.server.json",
          // 添加以下externals配置
          "externals": {
            "dialog-polyfill": "commonjs dialog-polyfill"
          }
        },
        // ... 保留原有其他配置
      }
    }
  }
}

3. 动态导入FirebaseUI,避免服务端静态加载

即使在ngOnInit中做了platformBrowser判断,静态导入仍会让Webpack在服务端打包时引入FirebaseUI依赖,改用动态导入:
修改login.component.ts的ngOnInit方法:

ngOnInit(): void {
  if (isPlatformBrowser(this.platformId)) {
    // 动态导入FirebaseUI核心及auth模块
    import('firebaseui').then(() => {
      import('firebaseui/auth').then(authModule => {
        const AuthUI = authModule.AuthUI;
        if (!this.ui) {
          this.ui = new AuthUI(this.auth as any);
        }
        // 原有uiConfig配置
        const uiConfig: firebaseui.auth.Config = {
          signInOptions: [
            EmailAuthProvider.PROVIDER_ID,
          ],
          signInSuccessUrl: '/',
          credentialHelper: firebaseui.auth.CredentialHelper.NONE,
          tosUrl: '/privacy-policy',
          privacyPolicyUrl: '/privacy-policy',
          callbacks: {
            signInSuccessWithAuthResult: (authResult: { user: User | null; }, redirectUrl: any) => {
              this.onLoginSuccessful(authResult.user);
              return false;
            },
            uiShown: () => {
              const loader = document.getElementById('loader');
              if (loader) {
                loader.style.display = 'none';
              }
            },
            signInFailure: (error: any) => {
              console.error(error);
              alert('Sign-in failed. Please try again with correct username & password.');
              return Promise.resolve();
            }
          },
          signInFlow: 'popup',
          autoUpgradeAnonymousUsers: true,
          siteName: 'My App'
        };
        this.ui.start('#firebaseui-auth-container', uiConfig);
        // 保留原有onAuthStateChanged逻辑
        this.auth.onAuthStateChanged((user: any) => {
          if (!user && this.ui.isPendingRedirect()) {
            this.ui.start('#firebaseui-auth-container', uiConfig);
          }
        });
      });
    });
  }
}

同时删除组件顶部的FirebaseUI静态导入语句(如import * as firebaseui from 'firebaseui';)。

4. 检查Home组件的间接依赖

错误栈显示问题关联到home.component.html,需检查Home组件是否间接引入了Auth相关逻辑或DOM元素,确保所有浏览器端代码都被isPlatformBrowser包裹,避免服务端渲染时触发依赖加载。

5. 清理缓存后重新构建

执行以下命令确保新配置生效:

rm -rf dist
npm run build:ssr
npm run dev:ssr

内容的提问来源于stack exchange,提问作者Robgit28

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 20:23:15