针对Gatsby / React SSR构建的JS对象的条件解构

时间:2018-10-24 17:07:11

标签: reactjs webpack gatsby destructuring ssr

以下基于AudioPlayer的组件react-media-player在Gatsby / React开发环境中非常有效。但是,将其构建到React SSR中真是令人难以置信。

AudioPlayer依赖于window对象进行实例化,Node.js无法使用该对象。因此,我不得不使用Gatsby的自定义Webpack配置来检测媒体播放器,并将null加载器投入混合。效果很好:

exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
  if (stage === "build-html") {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /react-media-player/,
            use: loaders.null(),
          },
        ],
      },
    })
  }
}

但是当您尝试通过Webpack进行构建时,现在出现错误:

  

WebpackError:TypeError:无法解构的属性CurrentTime   “未定义”或“空”。

这很有意义,因为我们刚删除了包含react-media-player的{​​{1}}。因此,如何确保Webpack不会尝试破坏此组件中的CurrentTime对象? (ES6中的other answers on S.O. regarding conditional destructuring对我来说毫无意义,所以请慢慢解释一下):

controls

1 个答案:

答案 0 :(得分:0)

对于未来的Google员工/感兴趣的人,要解决此问题,最终的解决方案需要花很多时间才能实现。我将逐步讲解它的所有工作,希望这对其他人有帮助:

AudioPlayer组件问题

  • 我认为react-media-player尤其难以使用SSR的原因是,它是组件的集合,而不仅仅是您插入的组件。对于我的构建,我正在创建一个音频播放器,因此包含Media库中的组件:PlayerCurrentTimeSeekBarDurationVolumereact-media-player,以及其他组件PlayPauseMuteUnmute

  • 我的自定义AudioPlayer组件需要进行三项{ typeof window !== 'undefined'}检查才能使我的构建正常工作,并将panneraudioContext变量重构为componentDidMount()。请注意,第一次window检查的是media而不是react-media-player,因为react-media-player从未在渲染函数中被调用。

AudioPlayer.js

import React, { Component } from 'react'
import { Media, Player, controls } from 'react-media-player'
import PlayPause from '../../components/AudioPlayerPlayPause'
import MuteUnmute from '../../components/AudioPlayerMuteUnmute'

const { CurrentTime, SeekBar, Duration, Volume } = controls
let panner = null

class AudioPlayer extends Component {

  componentDidMount() {
    const audioContext = new (window.AudioContext || window.webkitAudioContext)()
    panner = audioContext.createPanner()

    panner.setPosition(0, 0, 1)
    panner.panningModel = 'equalpower'
    panner.connect(audioContext.destination)

    const source = audioContext.createMediaElementSource(this._player.instance)
    source.connect(panner)
    panner.connect(audioContext.destination)
  }

  _handlePannerChange = ({ target }) => {
    const x = +target.value
    const y = 0
    const z = 1 - Math.abs(x)
    panner.setPosition(x, y, z)
  }

  render() {
    return (
      <div>
        { typeof window !== 'undefined' && Media && 
          <Media>   
            <div>
              <Player
                ref={c => this._player = c}
                src={this.props.src}
                useAudioObject
              />
              <section className="media-controls">
                <div className="media-title-box">
                  { typeof window !== 'undefined' && PlayPause && 
                    <PlayPause className="media-control media-control--play-pause"/>
                  }
                  <div className="media-title-content">
                    <div className="media-title">{ this.props.mediaTitle }</div>
                    <div className="media-subtitle">{ this.props.mediaSubtitle }</div>
                  </div>
                </div>
                <div className="media-controls-container">
                  <CurrentTime className="media-control media-control--current-time"/>
                  <SeekBar className="media-control media-control--volume-range"/>
                  <Duration className="media-control media-control--duration"/>
                </div>
                <div className="media-sound-controls">
                  { typeof window !== 'undefined' && MuteUnmute && 
                    <MuteUnmute className="media-control media-control--mute-unmute"/>
                  }
                  <Volume className="media-control media-control--volume"/>
                </div>            
              </section>
            </div>
          </Media>
        }
      </div>
    )
  }
}

export default AudioPlayer

PlayPause + MuteUnmute组件问题

  • react-media-player示例库中的这些组件在其中包含名为scalescaleX的子组件,它们是<transition>的非常简单的包装器,但最终被拧紧与构建。我基本上只是将它们重构为PlayPause和MuteUnmute组件本身,就解决了我在那里遇到的问题。

  • withMediaProps()包装了这些模块导出内容,从而使Webpack变得合适。我必须使用Gatsby空加载程序来解决该问题,因此在构建Webpack时不会寻找react-media-player

MuteUnmute.js:

import React, { Component } from 'react'
import { withMediaProps } from 'react-media-player'
import Transition from 'react-motion-ui-pack'

class MuteUnmute extends Component {
  _handleMuteUnmute = () => {
    this.props.media.muteUnmute()
  }

  render() {
    const { media: { volume }, className } = this.props
    return (
      <svg width="36px" height="36px" viewBox="0 0 36 36" className={className} onClick={this._handleMuteUnmute}>
        <circle fill="#eaebec" cx="18" cy="18" r="18"/>
        <polygon fill="#3b3b3b" points="11,14.844 11,21.442 14.202,21.442 17.656,25 17.656,11 14.074,14.844"/>
        <Transition
          component="g"
          enter={{ scale: 1 }}
          leave={{ scale: 0 }}
        >
          { volume >= 0.5 &&
            <path key="first-bar" fill="#3b3b3b" d="M24.024,14.443c-0.607-1.028-1.441-1.807-2.236-2.326c-0.405-0.252-0.796-0.448-1.153-0.597c-0.362-0.139-0.682-0.245-0.954-0.305c-0.058-0.018-0.104-0.023-0.158-0.035v1.202c0.2,0.052,0.421,0.124,0.672,0.22c0.298,0.125,0.622,0.289,0.961,0.497c0.662,0.434,1.359,1.084,1.864,1.94c0.26,0.424,0.448,0.904,0.599,1.401c0.139,0.538,0.193,0.903,0.216,1.616c-0.017,0.421-0.075,1.029-0.216,1.506c-0.151,0.497-0.339,0.977-0.599,1.401c-0.505,0.856-1.202,1.507-1.864,1.94c-0.339,0.209-0.663,0.373-0.961,0.497c-0.268,0.102-0.489,0.174-0.672,0.221v1.201c0.054-0.012,0.1-0.018,0.158-0.035c0.272-0.06,0.592-0.166,0.954-0.305c0.358-0.149,0.748-0.346,1.153-0.597c0.795-0.519,1.629-1.298,2.236-2.326C24.639,20.534,24.994,19.273,25,18C24.994,16.727,24.639,15.466,24.024,14.443z"/>
          }
        </Transition>
        <Transition
          component="g"
          enter={{ scale: 1 }}
          leave={{ scale: 0 }}
        >
          { volume > 0 &&
            <path key="second-bar" fill="#3b3b3b" d="M21.733,18c0-1.518-0.91-2.819-2.211-3.402v6.804C20.824,20.818,21.733,19.518,21.733,18z"/>
          }
        </Transition>
        <Transition
          component="g"
          enter={{ scale: 1 }}
          leave={{ scale: 0 }}
        >
          { volume === 0 &&
            <polygon key="mute" fill="#3b3b3b" points="24.839,15.955 23.778,14.895 21.733,16.94 19.688,14.895 18.628,15.955 20.673,18 18.628,20.045 19.688,21.106 21.733,19.061 23.778,21.106 24.839,20.045 22.794,18 "/>
          }
        </Transition>
      </svg>
    )
  }
}

export default withMediaProps(MuteUnmute)

PlayPause.js:

import React, { Component } from 'react'
import { withMediaProps } from 'react-media-player'
import Transition from 'react-motion-ui-pack'

class PlayPause extends Component {
  _handlePlayPause = () => {
    this.props.media.playPause()
  }

  render() {
    const { media: { isPlaying }, className } = this.props
    return (
      <svg
        role="button"
        width="36px"
        height="36px"
        viewBox="0 0 36 36"
        className={className}
        onClick={this._handlePlayPause}
      >
        <circle fill="#eaebec" cx="18" cy="18" r="18"/>
          <Transition
            component="g"
            enter={{ scaleX: 1 }}
            leave={{ scaleX: 0 }}
          >
            { isPlaying &&
              <g key="pause" style={{ transformOrigin: '0% 50%' }}>
                  <rect x="12" y="11" fill="#3b3b3b" width="4" height="14"/>
                  <rect x="20" y="11" fill="#3b3b3b" width="4" height="14"/>
              </g>
            }
          </Transition>
          <Transition
            component="g"
            enter={{ scaleX: 1 }}
            leave={{ scaleX: 0 }}
          >
            { !isPlaying &&
              <polygon
                key="play"
                fill="#3b3b3b"
                points="14,11 26,18 14,25"
                style={{ transformOrigin: '100% 50%' }}
              />
            }
          </Transition>
      </svg>
    )
  }
}

export default withMediaProps(PlayPause)

gatsby-node.js

/**
 * Implement Gatsby's Node APIs in this file.
 *
 * See: https://www.gatsbyjs.org/docs/node-apis/
 */

exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
  if (stage === "build-html") {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /AudioPlayer|PlayPause|MuteUnmute/,
            use: loaders.null(),
          },
        ],
      },
    })
  }
}

打包/构建问题

*我正在使用Node v10.12,无论出于何种原因都需要提高node-gyp。梳理完网络后,我最大的猜测就是我的全局node-gyp软件包与最新的Node不一致,因此一帆风顺。 npm install -g node-gyp然后node-gyp -v应该产生3.8.0。该版本使我的构建顺利进行。

package.json最终对我这样:

  "dependencies": {
    "gatsby": "^2.0.12",
    "gatsby-cli": "^2.4.3",
    "gatsby-link": "^2.0.2",
    "gatsby-plugin-manifest": "^2.0.4",
    "gatsby-plugin-react-helmet": "^3.0.0",
    "gatsby-plugin-sass": "^2.0.1",
    "node-sass": "^4.9.3",
    "prop-types": "^15.6.2",
    "react": "^16.4.2",
    "react-anchor-link-smooth-scroll": "^1.0.11",
    "react-dom": "^16.4.2",
    "react-helmet": "^5.2.0",
    "react-hot-loader": "^4.3.11",
    "react-media-player": "^0.7.1",
    "react-modal": "^3.5.1",
    "react-motion-ui-pack": "^0.10.3",
    "react-tabs": "^2.2.2",
    "react-transition-group": "^2.4.0",
    "react-waypoint": "^8.0.3"
  }

最后,向@pieh + @KyleAMathews和Gatsby团队的其余成员大声疾呼-Gatsby生态系统和支持仍然是OSS社区中最好的。我会继续努力。谢谢您给我们的一切。