简体   繁体   中英

Global variables in universal React app, Node throws ReferenceError

I'm creating a universal React application with Google sign in capabilities. Unfortunately, Google doesn't have a universal Google API ( gapi ) library that I can use on both client and server.

What I am really trying to do here is run my server code and have Babel recompile my code automatically when I make changes, and have it automatically restart the server (like nodemon does, but with a compilation step). I found a npm package called babel-watch that will do just this, however it doesn't integrate with webpack.

In my template index.html file, I have the following code in the HTML <head> :

index.html

<script type="text/javascript">
  window.gapiPromise = new Promise(resolve => window.gapiLoadedCallback = () => resolve(gapi))
</script>
<script src="https://apis.google.com/js/platform:auth2.js?onload=gapiLoadedCallback" async defer></script>

The above creates a new Promise which is resolved with the value of gapi when platform.js is loaded. However, since this is only loaded on the client, gapi and gapiPromise do not exist on the server.

I also have a Google Sign In React component that uses the global gapiPromise variable to do the button rendering when gapi is ready to be used:

GoogleSignIn.jsx

import React from 'react'

// should only run on client
if (gapiPromise !== false) {
  gapiPromise.then(gapi => {
    gapi.auth2.init({
      client_id: '[removed]'
    })
  })
}

class GoogleSignIn extends React.Component {
  constructor(props) {
    super(props)
  }
  componentDidMount() {
    // should only run on client
    if (gapiPromise !== false) {
     gapiPromise.then(gapi => gapi.signin2.render('g-signin2', {
        'scope': 'email',
        'width': 160,
        'height': 50,
        'theme': 'light',
        'onsuccess': this.props.onSuccess,
        'onfailure': this.props.onFailure
      }))   
    }
  }
  render() {
    return (
      <div className="google-sign-in">
        <div id="g-signin2"></div>
      </div>
    )
  }
}

export default GoogleSignIn

This works fine on the client, but when I try to render it on the server, Node complains that:

/Users/jreznik/Sites/my-app/dist/server.js:3568
  if (gapiPromise !== false) {
      ^

ReferenceError: gapiPromise is not defined
    at Object.defineProperty.value (/Users/jreznik/Sites/my-app/dist/server.js:3568:6)
    at __webpack_require__ (/Users/jreznik/Sites/my-app/dist/server.js:20:30)
    at Object.defineProperty.value (/Users/jreznik/Sites/my-app/dist/server.js:3408:22)
    at __webpack_require__ (/Users/jreznik/Sites/my-app/dist/server.js:20:30)
    at Object.defineProperty.value (/Users/jreznik/Sites/my-app/dist/server.js:3354:24)
    at __webpack_require__ (/Users/jreznik/Sites/my-app/dist/server.js:20:30)
    at Object.defineProperty.value (/Users/jreznik/Sites/my-app/dist/server.js:194:21)
    at __webpack_require__ (/Users/jreznik/Sites/my-app/dist/server.js:20:30)
    at Object.<anonymous> (/Users/jreznik/Sites/my-app/dist/server.js:59:16)
    at Object.<anonymous> (/Users/jreznik/Sites/my-app/dist/server.js:131:31)

I have tried prefixing gapiPromise in this file with global. (ie global.gapiPromise ), and then defining global.gapiPromise = false in my server's entry file ( server.js ), but then Node complains:

/Users/jreznik/Sites/my-app/dist/server.js:3569
      global.gapiPromise.then(function (gapi) {
                        ^

TypeError: Cannot read property 'then' of undefined

Finally, I was able to get it to work using webpack's DefinePlugin :

webpack.server.config.js

...

plugins: [
  new webpack.DefinePlugin({
    'window': {},
    'gapiPromise': false
  })
]

...

But if I do this then I can't use the babel-watch npm package to automatically recompile and restart the server.

How can I get Node to stop complaining about these undefined global variables?

It is a client library , which means that it is for use only with the DOM from the browser. It is actually equivalent to window.gapiPromise . And the window object represents an open window in a browser .

Ok after messing around with this for hours, I found a solution an hour after posting this question.

From my GoogleSignIn.jsx file, I moved the code out...

// should only run on client
if (gapiPromise !== false) {
  gapiPromise.then(gapi => {
    gapi.auth2.init({
      client_id: '[removed]'
    })
  })
}

... and into the template:

<script type="text/javascript">
    window.gapiPromise = new Promise(resolve => window.gapiLoadedCallback = () => {
        gapi.auth2.init({
            client_id: '[removed]'
        })
        resolve(gapi)
    })
</script>
<script src="https://apis.google.com/js/platform:auth2.js?onload=gapiLoadedCallback" async defer></script>

Not totally sure why Node doesn't complain about the extra gapiPromise s in the componentDidMount() method, but it's not. Also I changed the if-statement to check gapiPromise to:

if (typeof gapiPromise !== 'undefined') {

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM