Sagiv b.g
Sagiv b.g

Reputation: 31014

Import or require a bundled js file created by react application

Let's say I have a normal react application using redux and some ajax calls. If I want to pass it to someone I will give them the bundled js file I created with webpack and ask them to include it in their HTML + render a div with an id of "myApp" for example:

<div id="myApp"></div>

Ok, what if their website is also created with react, and they want to include my bundled js file inside one of their components, and of course render the relevant div?

I tried to use import or require to simulate this:
require('./path/to/myBundle.js');
import './path/to/myBundle.js';

Example:

    //...
    import './path/to/myBundle.js'; // the file that will render myApp to the relevant div
    // ....
    export function SomeApp(args){
            return(
                <div>  
                    <div id="myApp"></div>
                    <SomeComponent />
                </div>
            );
        };`

This does not work as I get some errors about:

Uncaught Error: Minified React error #37; visit http://facebook.github.io/react/docs/error-decoder.html?invariant=37 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

And when I visit this site I see:

_registerComponent(...): Target container is not a DOM element.

However, if they'll use this file (myBundle.js) outside their components (top level index.html for example) it will work just fine of course.

EDIT: I forgot to mention that I think I know what the problem is, the application doesn't have the HTML ready with this div yet. but I don't know a good and native way to wait for it to exist.

EDIT #2 following @Frxstrem 's answer: I'm trying to follow this answer but I think I'm doing it wrong. I have 2 copies of corry house slingshot demo app as app1 and app2. changed the 'output' on webpack.config.prod.js of app1 to:

  output: {
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/',
    filename: 'app1Bundle.js',
    library: "App1",
    libraryTarget: "umd"
  },

I'm trying to render app1 inside the homepage component of app2. so i copied the "published" files from app1 to the root of app2 and called the folder app1, then added an import call:

import {app1} from '../../app1/app1Bundle';

and a matching tag inside the return function:

const HomePage = () => {
  return (
    <div>
      <app1 />
      <h1>App 2</h1>
    </div>
  );
};

I get the same error as I posted above.

I also tried different combinations:

import app1 from '../../app1/app1Bundle'; // without curly braces

or even just getting the script as a normal js script

import '../../app1/app1Bundle';

or require('../../app1/app1Bundle');

and then tried to render a normal div tag with an id of "app1"

const HomePage = () => {
  return (
    <div>
      <div id="app1"></div>
      <h1>App 2</h1>
    </div>
  );
};

nothing seems to work as I still get the same error. I think the problem is the timing of the script load and the rendering of the elements. I think the div does not exist yet when the bundled script is searching for it.

Upvotes: 1

Views: 3414

Answers (2)

Sagiv b.g
Sagiv b.g

Reputation: 31014

The main problem i faced was to include the bundled js file of app1 after the DOM contains the target div it needs.
What i ended up doing was, creating a component in app2 project that will require() the bundled js file on componentDidMount() and will render and return the target div with a relevant id.

  • The reason i created a component is purely for re-usability purpose, instead of requiring this script with componentDidMount() on every component that needs it.

So, this is the component:

import React from 'react';

class AppOne extends React.Component {

  componentDidMount() {
    require('../app1/app1Bundle.js');
  }

  render() {
    return (
      <div id="app1"></div>
    );
  }
}
export default AppOne;

And this is how i use it in other component:

import React from 'react';
import AppOne from './AppOne';

const HomePage = () => {
  return (
    <div>
      <h1>App 2 - wrapper for app1</h1>
      <hr />
      <AppOne />
      <hr />
      <h1>This is App2 as well </h1>
    </div>
  );
};

export default HomePage;

It's working fine. my only concern is that i may face some conflicts with react because i'm using 2 react apps though for ow i don't see any errors.
I guess that's an issue for a different question.

EDIT: If someone will use this approach you should note that this will work only for the first load. because after the component will re-render itself the bundled script will not run again.

Upvotes: 0

Freyja
Freyja

Reputation: 40794

By default, Webpack will expose the entry module as a variable, which is useful when you include scripts with a <script> tag. (Because of this, if you require it you would likely just get {}.) However, if you want to load your bundle from other modules, you'll need to tell Webpack to expose it as an exported module instead.

The easiest way to do this is to set

{
  ...
  "libraryTarget": "umd"
}

in your Webpack configuration. With that, Webpack knows that it should expose your entry module as a module that can be required in Webpack, but can also be loaded with a <script> tag as necessary.

Webpack libraryTarget documentation

Upvotes: 2

Related Questions