Gatsby: Can I conditionally import a dependency in Gatsby?

Created on 16 Jan 2019  路  11Comments  路  Source: gatsbyjs/gatsby

Summary

An NPM package I wish to use seems to not support SSR very well. During a Gatsby Build I get

 WebpackError: ReferenceError: window is not defined

  - react-sliding-pane.js:1 Object../node_modules/react-sliding-pane/dist/react-sliding-pane.js
    [lib]/[react-sliding-pane]/dist/react-sliding-pane.js:1:418

I assume this is SSR as I have had this problem with other situations and gotten around it with the canonical typeof (window) !== 'undefined' check with great success.

Unfortunately before starting this Gatsby project I had very little JS experience (I'm a C++ guy) and have even less experience in modifying an NPM package and contributing PRs (At the moment I just don't have the bandwidth to learn all that). So, I was wondering if there was a way to get Gatsby or webpack to just ignore the import during SSR in one of my components?

I thought about putting it into gatsby-browser but then it'd be included everywhere which I do not want either.

Thanks

As an aside, is there any go-to place for learning how to develop or more importantly contribute to an npm package where I can later on go and learn everything I need to know? The info seems scattered.

question or discussion

All 11 comments

So, I was wondering if there was a way to get Gatsby or webpack to just ignore the import during SSR in one of my components?

Yes, using Webpack's dynamic imports. You only need to change your source file, Gatsby already configures Webpack correctly to enable dynamic imports.

If you need help with implementing this, we're always happy to help!

Is there any go-to place for learning how to develop or more importantly contribute to an npm package where I can later on go and learn everything I need to know?

There isn't really a go-to place, since there are a few different technologies in play there. First, you'll need to get comfortable with the core JavaScript language. I can recommend freeCodeCamp, YDKJS, or Wes Bos' ES6 course if you don't mind paying for a good course.

The next thing to learn is probably React since that's what a lot of front-end tools and sites today (including Gatsby) will use as their framework. For that the official docs and another Wes Bos course are good.

Then you can focus on the different tools such as Babel or Webpack, chances are you'll already encounter these while learning the previously mentioned technologies.

For a more in-depth roadmap you can check out grab/front-end-guide.

Thanks, will take a look at dynamic imports.

Edit: I started on that link and immediately it starts editing webpack.config.js - there is none in my Gatsby project.

So do I need to then add a onCreateWebpackConfig to gatsby-node.js? with some output: { chunkFilename...... etc} to it?

Then if I use import(...) in my code and end up with (default: _) how do I use that as a JSX component with properties? Or do I have to use raw React?

Re the other things, I'm getting pretty skilled now in general Javascript and React (JSX), having been embedded in them the last 6 months, it's more the framework layering and tooling that seems like an utter mess that does my head in. Given that is seems every "new thing"(tm) is yet another layer on top of other layers on layers, with version restrictions and language variations (TypeScript) and extreme fragility :) To be honest JS development feels like such an insurmountable mess

What I would like to see is a course that takes me from empty folder, to JS, typescript, to higher level frameworks like webpack and babel (And not just those) working together, and then writing an npm package and/or modifying one and contributing. Something I get a full end to end course in so I can actually trouble shoot problems and start contributing.

OK, so it looks like I have success. Can someone please quickly verify that I'm not doing something utterly stupid.

async componentDidMount() {
      if (isBrowser) {
        import(/* webpackChunkName: "react-sliding-pane" */ 'react-sliding-pane').then(({ default: slider }) => {
          this.setState({ slidingPane: slider })
        }).catch(error => 'An error occurred while loading the slider component')
      }
    }

Then in render() I have

      var slider = this.state.slidingPane &&
        React.createElement(this.state.slidingPane, {
          overlayClassName: "cartOverlayClass",
          isOpen: this.state.isOpen,
          className: "cartclass",
          title: "Cart",
          subtitle: "All prices are in USD",
          from: "right",
          width: "400px",
          onRequestClose: () => {
            // triggered on "<" on left top click or on outside click
            this.setState({ isOpen: false });
          }
        }, child components ........
    )

and of course, in render() I return my components like

return <SomeWrapper>{slider}<SomeWrapper>

Does this look OK?

First of all, I think it's a bad idea to store components in the state. Looking through the source code of react-sliding-pane, I can't see how it would interfere with SSR, so perhaps there is something wrong on your end. Could you share a reproduction repo?

A couple of other things I noticed, that aren't critical but you typically you wouldn't do like that:

  • You don't have to check for isBrowser in componentDidMount, since that lifecycle method will be run exclusively in the browser.
  • You don't need the /* webpackChunkName: "react-sliding-pane" */, import('react-sliding-pane') should suffice.
  • async keyword is unneccesary here as you don't use await inside the method. You may wish to read up on the differences between async/await and Promises (which is the name of the import().then pattern).

I started on that link and immediately it starts editing webpack.config.js - there is none in my Gatsby project.

That's what I was trying to say with "You only need to change your source file, Gatsby already configures Webpack correctly to enable dynamic imports". Gatsby provides a webpack.config.js under the hood so that you don't have to worry about configuring it yourself.

It's more the framework layering and tooling that seems like an utter mess that does my head in. Given that is seems every "new thing"(tm) is yet another layer on top of other layers on layers, with version restrictions and language variations (TypeScript) and extreme fragility :)

It's true that the JS ecosystem is pretty complex and fast moving, but you don't have to learn everything at once. It's important to know the core language, and if possible React. The rest will come with experience. IMO you shouldn't worry about Webpack, Babel, TypeScript etc. at this stage and just build a few apps with Gatsby or Create-React-App. Then once your needs exceed the stock configuration of those tools, you can get started with some of the lower-level stuff.

To be honest JS development feels like such an insurmountable mess

I would say the same about C++ :wink:

Thanks heaps for the information.

First of all, I think it's a bad idea to store components in the state. Looking through the source code of react-sliding-pane, I can't see how it would interfere with SSR, so perhaps there is something wrong on your end. Could you share a reproduction repo?

stored in the state so that render would be recalled once the import had completed, due to it being async. Is there another pattern I should use? Would I be better served using await (See below for why I used async) or just storing it in a member and using some other state to trigger rerender?

For a repo of the problem I'd have to work on one as I can't put up our code. Basically I was trying to do a gatsby build and had react-sliding-pane imported and would get that error (During import) about window not being defined. The code references window which is a no-no during SSR right? I've had other components cause trouble until I wrapped them in a check. Maybe I'm mixing terminology - I'm equating SSR with gatsby build

A couple of other things I noticed, that aren't critical but you typically you wouldn't do like that:

  • You don't have to check for isBrowser in componentDidMount, since that lifecycle method will be run exclusively in the browser.
    Ahh thanks, good to know. I'm, not entirely sure what is called or not in SSR
  • You don't need the /* webpackChunkName: "react-sliding-pane" */, import('react-sliding-pane') should suffice.

Yeah I wondered about that, I had just cut and pasted another example

  • async keyword is unneccesary here as you don't use await inside the method. You may wish to read up on the differences between async/await and Promises (which is the name of the import().then pattern).

Yes that was a typo :) I had originally had an await there and then decided not to and use .then()

It's true that the JS ecosystem is pretty complex and fast moving, but you don't have to learn everything at once. It's important to know the core language, and if possible React. The rest will come with experience. IMO you shouldn't worry about Webpack, Babel, TypeScript etc. at this stage and just build a few apps with Gatsby or Create-React-App. Then once your needs exceed the stock configuration of those tools, you can get started with some of the lower-level stuff.

I feel that I am way past just the basics (Although I bet I have holes in my knowledge) and I've being using a lot of lower level Gatsby stuff like gatsby-node, gatsby-browser, wrapWithProvider, client side private routes and things like that.

Being a C++ guy, I LOVE my type safety so it was a shame not being able to get TypeScript "really" working. The biggest headache for me is that everything is built on layers of dependencies so if I use Gatsby (Just as an example) I have to rely on Gatsby exposing all the things it uses like Babel, Webpack etc before I can configure custom Babel or Webpack stuff. If it doesn't, then all bets are off unless I can add the feature myself. It would be ideal if I was using a system that used Webpack that I could just configure Webpack directly, the way Webpack is normally configured and not have to learn general Webpack configuration and Gatsby Webpack configuration.

Anyway, my problem seems solved for now but one day I hope to figure out how to make react-slider-pane not use window during gatsby build and then I won't have to do dynamic imports which would be nice.

I would say the same about C++

I wouldn't necessarily argue with you there, it's daunting if you're new to it 馃槃

Hey @akoolenbourke

I think @jgierer12 already answered most of your questions so just adding a quick note here

Being a C++ guy, I LOVE my type safety so it was a shame not being able to get TypeScript "really" working.

You can add a gatsby plugin that will do all that is necessary for TypeScript support! https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-typescript

Hi @sidharthachatterjee

The thing with that plugin is that it's just linting (Which is nice), but I still have to manually run TS over the code first. I ideally want something automated that just added TS, and I go and write TS and it's transpiled for me as part of the build process,
Thanks

As far as I know and from the documentation, the plugin does transpile TypeScript

This plugin uses babel-plugin-transform-typescript to transpile typescript. It does not do type checking. Also since the TypeScript compiler is not involved, the following applies:

It does have some limitations though! I guess we could update the plugin to use https://github.com/TypeStrong/ts-loader maybe. Let me take a look.

In the mean time, closing this issue but please feel free to reopen or comment if any of your questions remain unanswered! Thank you @akoolenbourke

Sorry to revive this thread - but I've been having an issue in Gatsby with dynamic imports as well and still was not able to come up with a good resolution after picking through these comments (and other articles on this topic in Gatsby).

I've got them set up and working, but I'm unable to edit files that use the dynamically loaded components without triggering an error with my hot-reload during development. I'm using the dynamic imports as mentioned above like so:

import ComponentNotFound from "./component_not_found"

const ComponentList = {
  page: "page",
  grid: "grid",
  teaser: "teaser",
  feature: "feature",
}

async function ModuleLoader(type) {
  if (typeof ComponentList[type] === "undefined") {
    return ComponentNotFound
  }

  const blok = await import("./" + ComponentList[type])
    .then(({ default: item }) => {
      return item
    })
    .catch(error => "An error occurred while loading the slider component")

  return blok
}

export default ModuleLoader

In the template calling 'ModuleLoader' I have to set up a boolean in state to trigger when the promise is resolved and I'm able to load the component in render. Seems like during the hot-reload it doesn't think I have a component to render. I've tried setting up a conditional within render to check this, but it doesn't help.

It's not a major issue since it just seems to cause an error on hot-reload. If I manually refresh the browser it resolves the issue. But I'm worried that I'm screwing up webpack under the hood and I'm not satisfied with the developer experience going forward with a broken hot-reload.

I'm curious if anyone else has come across this issue, and how they may have resolved it. I appreciate any help anyone can provide!

I found that if I wanted to dynamically import any components. I had to do import them inside class components. If you import them in functional component , the props passed to imported component are not accessible.

Was this page helpful?
0 / 5 - 0 ratings