React Stripe Elements & SSR - Webpack Error: Window is Not Defined

React Stripe Elements & SSR - Webpack Error: Window is Not Defined

React Stripe Elements 在开发中运行良好,但通过 Netlify 实时部署会在 Provider.js react stripe 元素节点模块文件中抛出 'Webpack: Window is undefined'。

根据其他一些建议,我尝试了 ComponentDidMount 方法并使用以下方法编辑 Provider.js:

if (typeof window !== 'undefined') {
    let iInnerHeight = window.innerHeight;
}

两者仍然会导致部署失败。

此外,我尝试在 StripeProvider 组件中设置 stripe 或 apiKey,设置 stripe 会抛出需要 Stripe 对象的错误,例如Stripe(...) --> 当用这个 get Stripe is not defined 和 apiKey throws window undefined 错误切换时。

这是我的 gatsby-ssr.js 文件:

import React from 'react'
import { ShopkitProvider } from './src/shopkit'
import { StripeProvider, Elements } from 'react-stripe-elements'
import Layout from './src/components/Layout'

export const wrapRootElement = ({ element }) => {
  return (
      <StripeProvider apiKey={process.env.GATSBY_STRIPE_PUBLISHABLE_KEY}>
          <ShopkitProvider clientId{process.env.GATSBY_MOLTIN_CLIENT_ID}>
              <Elements>{element}</Elements> 
          </ShopkitProvider>
      </StripeProvider>
  )
}

export const wrapPageElement = ({ element, props }) => {
    return <Layout {...props}>{element}</Layout>
}

一切都按预期进行开发,但 SSR 存在 window Webpack 未定义的问题。我还在 Netlify 以及 .env 文件中设置了 env 变量

问题是在 StripeProvider 内检查 window 中的 Stripe 对象。这意味着您不能在 wrapRootElement 中直接使用它。简单的解决办法就是在gatsby-ssr.js里不用StripeProvider,只在gatsby-browser.js.

里用

但是,由于您使用多个服务提供商包装根,而且如果您像这样异步加载 Stripe:

// somewhere else                                      vvvvv
<script id="stripe-js" src="https://js.stripe.com/v3/" async />

您不妨制作一个可以在 gatsby-ssrgatsby-browser 中使用的通用包装器,这样更容易维护。

我通过为 StripeProvider 创建一个包装器来做到这一点,其中 Stripe 是根据 windowwindow.Stripe 的可用性手动启动的。然后 stripe 实例作为道具传递给 StripeProvider 而不是 api 键。

// pseudo
const StripeWrapper = ({ children }) => {
  let stripe,
  if (no window) stripe = null
  if (window.Stripe) stripe = window.Stripe(...)
  else {
    stripeLoadingScript.onload = () => window.Stripe(...)
  }
  return (
    <StripeProvider stripe={stripe}>
      {children}
    <StripeProvider>
  )
}

此逻辑应放在 componentDidMountuseEffect 挂钩中。这是一个带钩子的例子:

import React, { useState, useEffect } from 'react'
import { StripeProvider } from 'react-stripe-elements'

const StripeWrapper = ({ children }) => {
  const [ stripe, setStripe ] = useState(null)

  useEffect(() => {
    // for SSR
    if (typeof window == 'undefined') return

    // for browser
    if (window.Stripe) {
      setStripe(window.Stripe(process.env.STRIPE_PUBLIC_KEY))
    } else {
      const stripeScript = document.querySelector('#stripe-js')
      stripeScript.onload = () => {
        setStripe(window.Stripe(process.env.STRIPE_PUBLIC_KEY))
      }
    }
  }, []) // <-- passing in an empty array since I only want to run this hook once

  return (
    <StripeProvider stripe={stripe}>
      {children}
    </StripeProvider>
  )
}

// export a `wrapWithStripe` function that can used
// in both gatsby-ssr.js and gatsby-browser.js
const wrapWithStripe = ({ element }) => (
  <StripeWrapper>
    <OtherServiceProvider>
      {element}
    </OtherServiceProvider>
  </StripeWrapper>
)

通过在 gatsby-config.js

中将异步设置为 true
{
  resolve: `gatsby-plugin-stripe`,
  options: {
    async: true
  }
}

可以简化上面的代码。

const Stripe = props => {
    const [stripe, setStripe] = useState(null);

useEffect(() => {
    (async () => {
      const obj = await window.Stripe(process.env.STRIPE_PUBLIC_KEY);
      setStripe(obj);
    })();
  }, []);

  return (
    <>
      <StripeProvider stripe={stripe}>
         {children}
      </StripeProvider>
    </>
  );
};