francoischalifour / medium-zoom

🔎🖼 A JavaScript library for zooming images like Medium
https://medium-zoom.francoischalifour.com
MIT License
3.64k stars 165 forks source link

WebpackError: ReferenceError: window is not defined #81

Closed DrianHillman closed 5 years ago

DrianHillman commented 5 years ago

Bug description

👋🏽Hello! I've included this library as part of a Gatsby & React project, however my build fails when I import this package.

Step 1 of the Usage section is a simple import mediumZoom from 'medium-zoom'. When I add this import to the top of my JS file my build fails with the following response:

4:36:32 PM:   WebpackError: ReferenceError: window is not defined
4:36:32 PM:   
4:36:32 PM:   - medium-zoom.esm.js:1 Module../node_modules/medium-zoom/dist/medium-zoom.esm.    js
4:36:32 PM:     [lib]/[medium-zoom]/dist/medium-zoom.esm.js:1:1800

It looks like this is a common error where there's no window server-side but this package's code is being run & flagged by Webpack. Is there any way around this?

Gatsby has the following page with some possible leads to a solution— Debugging HTML Builds

I've tried the part about Fixing Third-party Modules, but to no avail. After editing the Webpack-Config the build completes, however, when calling the function the console says mediumZoom is not a function as if the import is ignored altogether.


How to reproduce

A list of the steps to reproduce the bug.

  1. Import medium-zoom per the docs
  2. Add mediumZoom(selector, options) call to ComponentDidMount (after the image elements are rendered)
  3. Works beautifully in Dev
  4. Run gatsby build to generate a production build
  5. See error

Expected behavior

The build should complete to deploy!

Environment

francoischalifour commented 5 years ago

Hey, thank you for the bug report. I'm not sure it's my responsibility to make the library SSR safe.

Did you try importing medium-zoom conditionally? Something along those lines:

const mediumZoom =
  typeof window !== 'undefined' ? require('medium-zoom').default : () => {}

Edit: see recommended solution.

DrianHillman commented 5 years ago

Hey @francoischalifour, thanks for pinging me back! I think we're on the right track and a solution like this would work. So far so good in my tests, but does the api then change in any way if using a require?

I've had to use mediumZoom.default('img.class') to get the function to fire. But when it does execute it's having difficulty closing & not functioning smoothly.

Here's a quick deploy to a Demo Page to see the difficulty it has opening & closing. Very strange! 🌀

Source

import React from 'react';
import { graphql } from 'gatsby';
import styled from 'styled-components';

var mediumZoom = Function.prototype;

if (typeof window !== 'undefined') {
  mediumZoom = require('medium-zoom');
}

//...

export default class Template extends React.Component {
  componentDidMount() {    
   console.log(mediumZoom.default); // {1}
    try {
      mediumZoom.default(document.querySelectorAll('.zoomable img'), {
        scrollOffset: 0,
        margin: 48,
        background: '#081c48dd',
      });
    } catch (e) {
      console.log("Couldn't execute from ComponentDidMount:", e);
    }
  }

  render() {
    const { data } = this.props;
    const { markdownRemark } = data; // data.markdownRemark holds our post data
    const { frontmatter, html } = markdownRemark;

    return (
      <>
        <Main>
          <div className='portfolio-post'>
            <h1>{frontmatter.title}</h1>
            <Content dangerouslySetInnerHTML={{ __html: html }} /> // {2}
          </div>
        </Main>
        <Footer />
      </>
    );
  }
}
// {2} Markdown Content 

### Demo

 <div class="img-gallery zoomable">
    <img src="ex-image-1.jpg" alt="Photo" title="Photo">
    <img src="ex-image-2.jpg" alt="Photo" title="Photo">
    <img src="ex-image-3.jpg" alt="Photo" title="Photo">
    <img src="ex-image-4.jpg" alt="Photo" title="Photo">
    <img src="ex-image-5.jpg" alt="Photo" title="Photo">
    <img src="ex-image-6.jpg" alt="Photo" title="Photo">
    <img src="ex-image-7.jpg" alt="Photo" title="Photo">
    <img src="ex-image-8.jpg" alt="Photo" title="Photo">
    <img src="ex-image-9.jpg" alt="Photo" title="Photo">
</div>
{1} console.log(mediumZoom) screenshot showing mediumZoom is a Module with a default function

Screenshot

francoischalifour commented 5 years ago

Indeed, I made a mistake in the code snippet (I updated it). When using require, you need to access the library with default. You can then use mediumZoom as usual.

Note that your images are heavy – the browser seems to struggle a bit on first load. I would recommend cropping them a little bit and compressing them. If you want to reduce the latency at first zoom, you can preload the images.

francoischalifour commented 5 years ago

I gave it a try @DrianHillman and my recommendation would be to use a dynamic import:

class Template extends React.Component {
  zoom = null

  componentDidMount() {
    import('medium-zoom').then(mediumZoom => {
      this.zoom = mediumZoom.default('.zoomable img')
    })
  }

  componentWillUnmount() {
    if (this.zoom) {
      this.zoom.detach()
    }
  }
}
DrianHillman commented 5 years ago

🎉 This is now working beautifully, François — thanks for helping troubleshoot! @francoischalifour