21

The problem:

We're facing an issue where any event listeners or global variables we attach to window does not seem to be there in our production build (but they are there in the development build).


Context:

We have a use case where our webapp renders inside a parent app. The parent app basically needs to tell us when we need to render our app. To do this, we have tried using event listeners as well as global functions.

How our app is loaded into the parent app

To render our webapp inside the parent app, the following happens:

  1. The parent app makes an HTTP call to myappwebsite.com/asset-manifest.json. This is like the default asset-manifest.json that gets created for any create-react-app webapp.
  2. The parent app finds the path to the main bundle.js from this asset manifest, and then makes an HTTP call to fetch our bundle.js using this path (e.g. myappwebsite.com/bundle.js).
  3. They dynamically inject a script tag into their head with its src set to our bundle.js to load our app's bundled javascript.
  4. They listen for the onload event of this new script element before trying to call any of our code or dispatching any events.

Attempt 1

Child app:

// index.tsx

window.addEventListener('renderApp', (event) => {
  console.log('This is running!'); // This only prints in `development`
  const { rootId } = event.detail;
  ReactDOM.render(<App />, document.getElementById(rootId));
})

Parent app:

// index.tsx

console.log('Dispatch is running!'); // This prints in both `development` and `production`
const renderEvent = new CustomEvent('renderApp', { detail: rootId: 'root' });

// This runs in the script.onload function to wait for our app to load
window.dispatchEvent(renderEvent);

This code works in the development build but not in the production build. In production, the dispatch does execute, but for some reason the event listener does not. When we tried to set some breakpoints on the minified production bundle, we noticed that the addEventListener code is there, however it is not actually executing... This is strange because the addEventListener is at the top level of the index.tsx in the child app and is not conditionally added.


Attempt 2

Child app:

// index.tsx

window.renderApp = (rootId) => {
  console.log('Child render is running'); // This only prints in `development`
  ReactDOM.render(<App />, document.getElementById(rootId));
})

Parent app:

// index.tsx

console.log('Render is running!'); // This prints in both `development` and `production`

// This runs in the script.onload function to wait for our app to load
window.renderApp(rootId);

Same issue here, the code works in the development build but not in the production build. In production, the call goes through but the global function does not seem to be on window. I am able to verify this by printing out window in the console.


It seems that any globals I set on window or any event listeners I set to it get ignored in the production build. Is this by Webpack's design? Or do you think something is messed up in my config/build script that is causing this to happen?

Mayank Kumar Chaudhari
  • 16,027
  • 10
  • 55
  • 122
Saad
  • 49,729
  • 21
  • 73
  • 112
  • > the global function does not seem to be on window < then this line should cause an error in production: `window.renderApp(rootId);` do you see console error? – glinda93 Sep 12 '21 at 07:18
  • @Saad have you checked for `cors` issue and there is no other application which register same name function to window – Chandan Sep 12 '21 at 18:48
  • @glinda93 Yeah, when I said "the global function does not seem to be on window" I meant that it is literally not on `window` and it causes an error. So it is confirmed that the function is not on `window`. @Chandan The function does not exist at all on `window`, there is no conflict. – Saad Sep 13 '21 at 02:30
  • `renderApp` is defined in Child component. It maybe called in parent before definition? Somehow it works in dev but in production definition order matters... – glinda93 Sep 13 '21 at 05:33
  • This sounds more like a race condition issue more than a webpack issue. Both solutions should work in theory. Are you sure the parent calls the renderApp function/event after this is actually initialised? – Phil Sep 13 '21 at 09:07
  • 1
    Minimal reproducible example would be nice. – x00 Sep 13 '21 at 12:23
  • We are setting a bunch of things on `window` and this problem never occured for us. Could you `console.log` in the line before you are registering `window.renderApp` and in the line before you are calling it? I'm thinking a race condition like @Phil suggested is the most likely cause. – Taxel Sep 13 '21 at 12:51
  • Can you try setting `target: "node"` instead of `target:"web"`? – Nidhi Dadiya Sep 14 '21 at 17:03
  • Minimal repro may be hard since there are a lot of individual pieces that may be causing this issue. I'll be updating the question with more context with our script is loaded, hopefully that clarifies some stuff up. – Saad Sep 15 '21 at 01:34
  • Hi @glinda93, @Phil, @Taxel, and others, I've added a "How our app is loaded into the parent app" section above. This contains some pretty important context, sorry for not including it in the original post. I am wondering if dynamically loading our script tag is the issue with `window` not getting populated correctly. It's still weird that the issue doesn't happen in the `development` bundle though. – Saad Sep 15 '21 at 01:41
  • @Saad Is child component rendered inside an iframe? – glinda93 Sep 15 '21 at 02:51
  • No, the parent app has an empty div element in their HTML with an `id` of `rootId` which is what gets passed to the child app. It is used successfully in beta only... – Saad Sep 15 '21 at 19:43
  • According to your question, you should be having 2 entry points in the webpack one for parent another for the child... However, your webpack config `entry: './src/index',` seems to have a single entry point. Am I on the same page? Am I missing something? Where are you importing index.js for child? – ChandraKumar Sep 16 '21 at 17:20
  • I've created minimal reproduceable example for your case https://github.com/stackoverflow-answers/q-69126274. it renders child element in dev & build that clarifies custom global variables are available in build. – Moorthy G Sep 17 '21 at 06:39
  • @Saad in child app add logging before after event listener. that will help to figure out if event was actually registered before event was triggered from parent. – Zeeshan Anjum Sep 17 '21 at 18:26
  • you could try removing the conditional configs 1 by 1 in your webpack file to see which is causing the issue in prod – diedu Sep 18 '21 at 12:04
  • @Saad Can you share the production url? – glinda93 Sep 18 '21 at 17:21
  • Is it possible your webpack dev build to be 1 file and prod 2 or more? Maybe order of execution is being mangled for some reason. – Martin Chaov Oct 08 '21 at 07:25

3 Answers3

1

The problem ended up being an issue where the output of our bundle was separated into main.js and vendor.js, but we were only loading the main.js into the parent app.

Saad
  • 49,729
  • 21
  • 73
  • 112
0

Add a node entry with global: true to your config:

node: {
    global: true,
    __filename: false,
    __dirname: false,
  },
DoneDeal0
  • 5,273
  • 13
  • 55
  • 114
  • Doesn't this just create some Node polyfills? https://webpack.js.org/configuration/node/#nodeglobal – Saad Sep 15 '21 at 01:44
  • Yes, but this trick once solved a similar issue than yours in my project. Sorry if it doesn't work. – DoneDeal0 Sep 15 '21 at 06:59
0

Webpack does behave a bit strange when it comes to windows object and we can site number of discussions on this matter.

According to this answer adding globalObject: 'this' line to the output section in webpack config file might fix the issue.

Other references https://github.com/webpack/webpack/issues/7112 https://github.com/markdalgleish/static-site-generator-webpack-plugin/issues/130

Mayank Kumar Chaudhari
  • 16,027
  • 10
  • 55
  • 122