Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ReferenceError: document is not defined #1113

Closed
alexseitsinger opened this issue Dec 16, 2018 · 12 comments
Closed

ReferenceError: document is not defined #1113

alexseitsinger opened this issue Dec 16, 2018 · 12 comments

Comments

@alexseitsinger
Copy link

Relevant code:

package.json

        ...
	"main": "dist/main.js",
	"files": ["dist/", "package.json", "README.md"],
	"devDependencies": {
		"@babel/core": "^7.2.2",
		"@babel/plugin-proposal-class-properties": "^7.2.1",
		"@babel/preset-env": "^7.2.0",
		"@babel/preset-react": "^7.0.0",
		"babel-core": "7.0.0-bridge.0",
		"babel-jest": "^23.6.0",
		"babel-loader": "^8.0.4",
		"babel-plugin-emotion": "^10.0.5",
		"enzyme": "^3.8.0",
		"enzyme-adapter-react-16": "^1.7.1",
		"jest": "^23.6.0",
		"jest-emotion": "^10.0.5",
		"webpack": "^4.27.1",
		"webpack-cli": "^3.1.2"
	},
	"dependencies": {
		"@emotion/core": "^10.0.5",
		"@emotion/styled": "^10.0.5",
		"debounce": "^1.2.0",
		"prop-types": "^15.6.2",
		"react": "^16.6.3",
		"react-dom": "^16.6.3"
	},
	"scripts": {
		"build": "./node_modules/.bin/webpack --config=webpack.config.js",
		"test": "./node_modules/.bin/jest"
	}
        ...

webpack.config.js

const path = require("path")

module.exports = {
	entry: "./src/index.js",
	mode: "production",
	output: {
		path: path.resolve("./dist"),
		filename: "[name].js",
		libraryTarget: "commonjs2",
		libraryExport: "default"
	},
	module: {
		rules: [
			{
				test: /\.jsx?$/,
				exclude: /node_modules/,
				use: "babel-loader"
			}
		]
	}
}

src/components/body/elements.js

import styled from "@emotion/styled"
import { css, jsx } from "@emotion/core"

export const Wrapper = styled.div`
  background-color: red;
`

src/components/body/index.js

import { Wrapper } from "./elements"

class Body extends React.Component {
	render() {
		const {
			children,
			containerClassName,
			layerIndex
		} = this.props
		return <Wrapper>{children}</Wrapper>
	}
}

src/index.js

import React from "react"
import ReactDOM from "react-dom"
import PropTypes from "prop-types"
import { debounce } from "debounce"

import Body from "./components/body"

const isBrowser = typeof document !== "undefined" ? true : null

class SimpleModal extends React.Component {
	static defaultProps = {
		mountPointSelector: "body",
		containerClassName: "SimpleModal",
		layerPosition: "above",
		defaultIndex: 100
	}
	_getLayerIndex = () => {
		const { layerPosition, defaultIndex } = this.props
		const allModals = this._getOtherModals()
		const totalModals = allModals && allModals.length ? allModals.length : 0
		var nextIndex = defaultIndex
		if (layerPosition === "above") {
			nextIndex += totalModals
		}
		if (layerPosition === "below") {
			nextIndex -= totalModals
		}
		return nextIndex
	}
	_getOtherModals = (modal) => {
		if (!isBrowser) {
			return
		}
		const { containerClassName } = this.props
		// Get all the elements on the page with the classname modal.
		const otherModals = [].slice.call(
			document.getElementsByClassName(containerClassName)
		)
		// If we got ourselves as an argument, rmeove it from the list of
		// elements we return.
		if (modal) {
			return otherModals.filter((el) => {
				return el !== modal
			})
		}
		// Otherwise, return them all.
		return otherModals
	}
	_getMountPoint = () => {
		if (!isBrowser) {
			return
		}
		const { mountPointSelector } = this.props
		return document.querySelector(mountPointSelector)
	}
	render() {
		const {
			children,
			isVisible,
			containerClassName
		} = this.props
		// If the element is visible...
		if (isVisible) {
                        if(!isBrowser){
                                return null
                        }
			// Then, create the portal element in the DOM, under the BODY.
			const mountPoint = this._getMountPoint()
			const layerIndex = this._getLayerIndex()
			const modal = (
				<Body containerClassName={containerClassName} layerIndex={layerIndex}>
					{children}
				</Body>
			)
			return ReactDOM.createPortal(modal, mountPoint)
		} else {
			return null
		}
	}
}

export default SimpleModal

What you did:
I am attempting to rebuild a package that I maintain. However, the default component will function after being compiled by webpack. It will render correctly after a jest test though. Any attempt to use this package in another project results in a ReferenceError at runtime. I have pieced the <SimpleModal> component back together, to try and find the culprit, and it's the styled <Wrapper> element.

What happened:
Rendering <Wrapper> as an element inside <Body> component raises a ReferenceError: document is not defined.

@alexseitsinger alexseitsinger changed the title ReferenceError: Document is not defined ReferenceError: document is not defined Dec 16, 2018
@Andarist
Copy link
Member

Could u prepare a repository with the issue reproduced?

@alexseitsinger
Copy link
Author

alexseitsinger commented Dec 18, 2018

I will if the following isn't sufficient. I realize the module is named .browser, so I'm either doing something wrong (with SSR perhaps) or there's an error with the following module.

The issue seems to be with the function createCache located at https://github.com/emotion-js/emotion/blob/master/packages/cache/src/index.js

Uncompiled source

let createCache = (options?: Options): EmotionCache => {
  if (options === undefined) options = {}
  let key = options.key || 'css'
  let stylisOptions

  if (options.prefix !== undefined) {
    stylisOptions = {
      prefix: options.prefix
    }
  }

  let stylis = new Stylis(stylisOptions)

  if (process.env.NODE_ENV !== 'production') {
    // $FlowFixMe
    if (/[^a-z-]/.test(key)) {
      throw new Error(
        `Emotion key must only contain lower case alphabetical characters and - but "${key}" was passed`
      )
    }
  }
  let inserted = {}
  // $FlowFixMe
  let container: HTMLElement
  if (isBrowser) {
    container = options.container || document.head

    const nodes = document.querySelectorAll(`style[data-emotion-${key}]`)

    Array.prototype.forEach.call(nodes, (node: HTMLStyleElement) => {
      const attrib = node.getAttribute(`data-emotion-${key}`)
      // $FlowFixMe
      attrib.split(' ').forEach(id => {
        inserted[id] = true
      })
      if (node.parentNode !== container) {
        container.appendChild(node)
      }
    })
  }

  let insert: (
    selector: string,
    serialized: SerializedStyles,
    sheet: StyleSheet,
    shouldCache: boolean
  ) => string | void

  if (isBrowser) {
    stylis.use(options.stylisPlugins)(ruleSheet)

    insert = (
      selector: string,
      serialized: SerializedStyles,
      sheet: StyleSheet,
      shouldCache: boolean
    ): void => {
      let name = serialized.name
      Sheet.current = sheet
      if (
        process.env.NODE_ENV !== 'production' &&
        serialized.map !== undefined
      ) {
        let map = serialized.map
        Sheet.current = {
          insert: (rule: string) => {
            sheet.insert(rule + map)
          }
        }
      }
      stylis(selector, serialized.styles)
      if (shouldCache) {
        cache.inserted[name] = true
      }
    }
  } else {
    stylis.use(removeLabel)
    let serverStylisCache = rootServerStylisCache
    if (options.stylisPlugins || options.prefix !== undefined) {
      stylis.use(options.stylisPlugins)
      // $FlowFixMe
      serverStylisCache = getServerStylisCache(
        options.stylisPlugins || rootServerStylisCache
      )(options.prefix)
    }
    let getRules = (selector: string, serialized: SerializedStyles): string => {
      let name = serialized.name
      if (serverStylisCache[name] === undefined) {
        serverStylisCache[name] = stylis(selector, serialized.styles)
      }
      return serverStylisCache[name]
    }
    insert = (
      selector: string,
      serialized: SerializedStyles,
      sheet: StyleSheet,
      shouldCache: boolean
    ): string | void => {
      let name = serialized.name
      let rules = getRules(selector, serialized)
      if (cache.compat === undefined) {
        // in regular mode, we don't set the styles on the inserted cache
        // since we don't need to and that would be wasting memory
        // we return them so that they are rendered in a style tag
        if (shouldCache) {
          cache.inserted[name] = true
        }
        if (
          // using === development instead of !== production
          // because if people do ssr in tests, the source maps showing up would be annoying
          process.env.NODE_ENV === 'development' &&
          serialized.map !== undefined
        ) {
          return rules + serialized.map
        }
        return rules
      } else {
        // in compat mode, we put the styles on the inserted cache so
        // that emotion-server can pull out the styles
        // except when we don't want to cache it(just the Global component right now)

        if (shouldCache) {
          cache.inserted[name] = rules
        } else {
          return rules
        }
      }
    }
  }

  if (process.env.NODE_ENV !== 'production') {
    // https://esbench.com/bench/5bf7371a4cd7e6009ef61d0a
    const commentStart = /\/\*/g
    const commentEnd = /\*\//g

    stylis.use((context, content) => {
      switch (context) {
        case -1: {
          while (commentStart.test(content)) {
            commentEnd.lastIndex = commentStart.lastIndex

            if (commentEnd.test(content)) {
              commentStart.lastIndex = commentEnd.lastIndex
              continue
            }

            throw new Error(
              'Your styles have an unterminated comment ("/*" without corresponding "*/").'
            )
          }

          commentStart.lastIndex = 0
          break
        }
      }
    })

    stylis.use((context, content, selectors) => {
      switch (context) {
        case 2: {
          for (let i = 0, len = selectors.length; len > i; i++) {
            // :last-child isn't included here since it's safe
            // because a style element will never be the last element
            let match = selectors[i].match(/:(first|nth|nth-last)-child/)
            if (match !== null) {
              console.error(
                `The pseudo class "${
                  match[0]
                }" is potentially unsafe when doing server-side rendering. Try changing it to "${
                  match[1]
                }-of-type"`
              )
            }
          }
          break
        }
      }
    })
  }

  const cache: EmotionCache = {
    key,
    sheet: new StyleSheet({
      key,
      container,
      nonce: options.nonce,
      speedy: options.speedy
    }),
    nonce: options.nonce,
    inserted,
    registered: {},
    insert
  }
  return cache
}

Distributed source
(./node_modules/@emotion/cache/dist/cache.browser.esm.js)

var createCache = function createCache(options) {
  if (options === undefined) options = {};
  var key = options.key || 'css';
  var stylisOptions;

  if (options.prefix !== undefined) {
    stylisOptions = {
      prefix: options.prefix
    };
  }

  var stylis = new Stylis(stylisOptions);

  if (process.env.NODE_ENV !== 'production') {
    // $FlowFixMe
    if (/[^a-z-]/.test(key)) {
      throw new Error("Emotion key must only contain lower case alphabetical characters and - but \"" + key + "\" was passed");
    }
  }

  var inserted = {}; // $FlowFixMe

  var container;

  {
    container = options.container || document.head;
    var nodes = document.querySelectorAll("style[data-emotion-" + key + "]");
    Array.prototype.forEach.call(nodes, function (node) {
      var attrib = node.getAttribute("data-emotion-" + key); // $FlowFixMe

      attrib.split(' ').forEach(function (id) {
        inserted[id] = true;
      });

      if (node.parentNode !== container) {
        container.appendChild(node);
      }
    });
  }

  var _insert;

  {
    stylis.use(options.stylisPlugins)(ruleSheet);

    _insert = function insert(selector, serialized, sheet, shouldCache) {
      var name = serialized.name;
      Sheet.current = sheet;

      if (process.env.NODE_ENV !== 'production' && serialized.map !== undefined) {
        var map = serialized.map;
        Sheet.current = {
          insert: function insert(rule) {
            sheet.insert(rule + map);
          }
        };
      }

      stylis(selector, serialized.styles);

      if (shouldCache) {
        cache.inserted[name] = true;
      }
    };
  }

  if (process.env.NODE_ENV !== 'production') {
    // https://esbench.com/bench/5bf7371a4cd7e6009ef61d0a
    var commentStart = /\/\*/g;
    var commentEnd = /\*\//g;
    stylis.use(function (context, content) {
      switch (context) {
        case -1:
          {
            while (commentStart.test(content)) {
              commentEnd.lastIndex = commentStart.lastIndex;

              if (commentEnd.test(content)) {
                commentStart.lastIndex = commentEnd.lastIndex;
                continue;
              }

              throw new Error('Your styles have an unterminated comment ("/*" without corresponding "*/").');
            }

            commentStart.lastIndex = 0;
            break;
          }
      }
    });
    stylis.use(function (context, content, selectors) {
      switch (context) {
        case 2:
          {
            for (var i = 0, len = selectors.length; len > i; i++) {
              // :last-child isn't included here since it's safe
              // because a style element will never be the last element
              var match = selectors[i].match(/:(first|nth|nth-last)-child/);

              if (match !== null) {
                console.error("The pseudo class \"" + match[0] + "\" is potentially unsafe when doing server-side rendering. Try changing it to \"" + match[1] + "-of-type\"");
              }
            }

            break;
          }
      }
    });
  }

  var cache = {
    key: key,
    sheet: new StyleSheet({
      key: key,
      container: container,
      nonce: options.nonce,
      speedy: options.speedy
    }),
    nonce: options.nonce,
    inserted: inserted,
    registered: {},
    insert: _insert
  };
  return cache;
};

I'm receiving an error that and leaves the following stack trace:

Message: document is not defined

Stack trace: ReferenceError: document is not defined
    at createCache (webpack:///./node_modules/@emotion/cache/dist/cache.browser.esm.js?:109:38)
    at eval (webpack:///./node_modules/@emotion/core/dist/core.browser.esm.js?:32:149)
    at Module../node_modules/@emotion/core/dist/core.browser.esm.js (/usr/home/main/mnt/da0/code/projects/library/alexseitsinger/sites/main/node_modules/@alexseitsinger/simple-modal/dist/main.js:110:1)
    at __webpack_require__ (/usr/home/main/mnt/da0/code/projects/library/alexseitsinger/sites/main/node_modules/@alexseitsinger/simple-modal/dist/main.js:21:30)
    at eval (webpack:///./node_modules/@emotion/styled-base/dist/styled-base.browser.esm.js?:7:71)
    at Module../node_modules/@emotion/styled-base/dist/styled-base.browser.esm.js (/usr/home/main/mnt/da0/code/projects/library/alexseitsinger/sites/main/node_modules/@alexseitsinger/simple-modal/dist/main.js:194:1)
    at __webpack_require__ (/usr/home/main/mnt/da0/code/projects/library/alexseitsinger/sites/main/node_modules/@alexseitsinger/simple-modal/dist/main.js:21:30)
    at eval (webpack:///./src/components/body/elements.js?:6:78)
    at Module../src/components/body/elements.js (/usr/home/main/mnt/da0/code/projects/library/alexseitsinger/sites/main/node_modules/@alexseitsinger/simple-modal/dist/main.js:431:1)
    at __webpack_require__ (/usr/home/main/mnt/da0/code/projects/library/alexseitsinger/sites/main/node_modules/@alexseitsinger/simple-modal/dist/main.js:21:30)

@Andarist
Copy link
Member

You have mentioned SSR - are those .browser files imported when trying to run this in node?

@alexseitsinger
Copy link
Author

The project this is used in uses SSR. An example project layout can be found here. In the frontend/src folder is a render.js file. This is where the app is passed in and rendered for the server-side bundle. The client-side bundle is rendered from client.js. Both of these entry points import a module <App/> which also includes elements.js modules that use @emotion/styled and @emotion/core to create styled components. Nothing unusual was done when upgrading to emotion 10 other changing the import statements used (following the documentation), but somehow these errors occur now.

@Andarist
Copy link
Member

Could u specify target: 'node' in your webpack config? https://webpack.js.org/concepts/targets/#usage

@alexseitsinger
Copy link
Author

That did it. Thanks!

@dachinat
Copy link

What about react-rails? I tried setting

environment.config.merge({
    target: 'node'
});

in rails webpacker, but then I was getting require is not a function

@dachinat
Copy link

i don't think changing a target is good option.

@617dev
Copy link

617dev commented Feb 22, 2019

i'm trying to get emotion working with this SSR solution https://github.com/Limenius/ReactBundle, and as soon as I import emotion (without even using it) i get this ReferenceError: document is not defined error. like @dachinat said, changing the target in webpack is not an option because like him i then get require is not a function. I've tried using the older emotion SSR api's, doesn't seem to help... Any help or even clues are appreciated! thanks

@kkorach
Copy link

kkorach commented Feb 22, 2019

I ran into the same problem when trying to use v10 with react-on-rails. v9 worked fine. Switching the webpack target unfortunately isn't an option for us. I haven't had a chance to dig into the source too much, but somewhere the test of whether we are in a browser or not changed between v9 and v10.

@jesster2k10
Copy link

I ran into the same problem when trying to use v10 with react-on-rails. v9 worked fine. Switching the webpack target unfortunately isn't an option for us. I haven't had a chance to dig into the source too much, but somewhere the test of whether we are in a browser or not changed between v9 and v10.

Did you ever manage to solve this?

@LorenDorez
Copy link

Anyone find a solution to this? Changing our Webpack target isnt an option for us right now because of our BrowserList rules

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants