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
相关产品推荐
相关产品推荐

