Angular Universal 中如何处理只在浏览器中运行的第三方库?
我在用 Angular Universal 做 SSR,但项目里引入了一个只支持浏览器环境的第三方图表库,一跑服务端就报 window is not defined。我试过用 PLATFORM_ID 判断环境,但组件里还是报错,不知道是不是写法有问题。
比如下面这段 Vue 写法(我们团队混用了部分 Vue 组件做对比),在 mounted 里才调用库,就不会出问题:
<template>
<div ref="chartContainer"></div>
</template>
<script>
export default {
mounted() {
// 只在浏览器中初始化
initChart(this.$refs.chartContainer);
}
}
</script>
核心思路是:延迟执行浏览器专属代码,直到应用完全在浏览器环境运行。在Angular里我们需要结合这几个点:
1. 使用PLATFORM_ID判断环境是必须的,但光这样不够
2. 需要配合Angular的生命周期钩子
3. 懒加载第三方库
具体实现可以这样:
为什么这样做有效?
1. PLATFORM_ID能正确识别当前环境,避免服务端执行
2. ViewChild的static:false确保在变更检测后获取元素引用
3. 动态import避免了SSR构建时的依赖分析问题
需要注意的坑点:
1. 不要用ngAfterViewInit,因为它在服务端也会触发
2. 静态属性要设false,否则服务端渲染会报错
3. 如果库特别大,可以考虑用IntersectionObserver实现懒加载
如果遇到特别顽固的库,终极方案是封装成懒加载的Web Component。我们项目里有个3D库就是这么处理的,虽然麻烦但一劳永逸。
1. 先用isPlatformBrowser判断环境,把整个组件包起来:
2. 但有时候组件初始化时还是会报错,因为Angular会在服务端预渲染模板。这时候得用动态导入+懒加载:
3. 还有个更骚的操作是用Angular的BrowserModule.withServerTransition,不过我建议先试试前两种方法。
你遇到的window报错多半是因为库在import时就执行了window操作。记得一定不要在组件顶部直接import第三方库,要放在方法里动态导入。
话说这些图表库为啥就不能考虑下SSR呢...每次都要这样绕来绕去好烦啊。