esbuild icon indicating copy to clipboard operation
esbuild copied to clipboard

When using iife format, the `module` and `exports` variables conflict with the code of other compilation tools.

Open peakchen90 opened this issue 2 years ago • 7 comments

The umd code I built using webpack and compressed using esbuild reported an error that the variable require could not be found.

Usage:

const esbuild = require('esbuild');
const fs = require('fs');

(async () => {
  console.log('\n-------------- source code --------------');
  const content = fs.readFileSync('./index.js', 'utf-8');
  console.log(content);

  console.log('\n--------------- output -----------------');
  await esbuild.build({
    entryPoints: ['./index'],
    format: 'iife',
  });
})();

Output:


-------------- source code --------------
(function webpackUniversalModuleDefinition(root, factory) {
  if (typeof exports === 'object' && typeof module === 'object') {
    factory(require('React'));
  } else if (typeof exports === 'object') {
    factory(require('React'));
  } else {
    factory(root['React']);
  }
})(self, function (module_react) {
  return function () {
    var __webpack_modules__ = {
      '../kone/node_modules/ansi-html-community/index.js': function (module) {
        /* module code */
      },
    };
    return __webpack_modules__;
  };
});


--------------- output -----------------
"use strict";
(() => {
  var __getOwnPropNames = Object.getOwnPropertyNames;
  var __commonJS = (cb, mod) => function __require() {
    return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
  };
  var require_kone = __commonJS({
    "index.js"(exports, module) {
      (function webpackUniversalModuleDefinition(root, factory) {
        if (typeof exports === "object" && typeof module === "object") {
          factory(require("React"));
        } else if (typeof exports === "object") {
          factory(require("React"));
        } else {
          factory(root["React"]);
        }
      })(self, function(module_react) {
        return function() {
          var __webpack_modules__ = {
            "../kone/node_modules/ansi-html-community/index.js": function(module2) {
            }
          };
          return __webpack_modules__;
        };
      });
    }
  });
  require_kone();
})();

image

These two variables should not be correlated.

peakchen90 avatar Dec 25 '23 03:12 peakchen90

Since you're not enabling bundling, the require() expressions are left as is (i.e. not bundled) and doing so in the bare browser environment will throw a ReferenceError: require is not defined. The behavior seems correct to me.

hyrious avatar Dec 25 '23 03:12 hyrious

@josevalim But I can't enable bundle. It should be a react module that is provided globally.

peakchen90 avatar Dec 25 '23 04:12 peakchen90

I see, you want to make the react module to become a global name React? If so, you can write a custom plugin to replace the react module with module.exports = React; (the name React is referencing a global scope variable). For a full example you can see https://github.com/evanw/esbuild/issues/337#issuecomment-754840414.

hyrious avatar Dec 27 '23 01:12 hyrious

The umd code I built using webpack and compressed using esbuild

If you are just using esbuild for minification (what you are calling compression), then it seems to make the most sense for Webpack to be the one to handle require, not esbuild. Minification is a transformation that rewrites the code to be smaller but have equivalent behavior. You shouldn't need to specify --format=iife at all to get esbuild's minification to work. Here's an example: (link)

evanw avatar Dec 27 '23 18:12 evanw

The umd code I built using webpack and compressed using esbuild

If you are just using esbuild for minification (what you are calling compression), then it seems to make the most sense for Webpack to be the one to handle require, not esbuild. Minification is a transformation that rewrites the code to be smaller but have equivalent behavior. You shouldn't need to specify --format=iife at all to get esbuild's minification to work. Here's an example: (link)

I did not specify the --format=iife to compress the code at first, but found that some global variables will be exposed after compression (see faq/#top-level-var).

I will load the js resources of multiple sub-app in a page document (micro frontend), and then these exposed global variables will conflict and cause some problems, so I have to manually wrap it with iife outside the code after minification.

Recently I tried to add the --format=iife to the compressed code, but found that it conflicts with the module and require variables of webpack.

peakchen90 avatar Dec 28 '23 04:12 peakchen90

Ah, I see. I was mentioning this because the example code you posted is already wrapped in an IIFE, and has no top-level variables:

(function webpackUniversalModuleDefinition(root, factory) {
  ...
})(self, function (module_react) {
  ...
});

So in that case there's no need for --format=iife since there are no top-level variables.

The --format= flag treats the input as a module (which is auto-detected to be either CJS or ESM) and then converts it to the specified target format. In this case the module is detected as CJS and then converted to the IIFE format. When that happens, any require calls that aren't in the bundle (which is all of them if bundling is disabled) are forwarded on to the require global. That allows you to assign your own shim to window.require and intercept those require calls. For example, you could add this to your HTML if you wanted to:

<script>
window.require = function(name) {
  if (name === "react") {
    return window.globalReactObjectFromSomewhere
  }
  throw Error("Don't know how to require " + name)
}
</script>

evanw avatar Dec 29 '23 18:12 evanw

@evanw Sorry, maybe I didn't make it clear.

  1. First I use webpack umd format to compile the project code.
  2. Then use esbuild to compress the code output in the previous step, but found that in some cases some variables will be exposed to global.
  3. Checking the esbuild document found that this is expected (see faq/#top-level-var), but it may conflict in two different projects (See last 2 screenshots).
  4. In order to solve the problem of global variable conflicts exposed by different projects, I used (() => { /* esbuild minification code */ })() to wrap the esbuild compressed code, which perfectly solved my problem.
  5. A few weeks later, I found that --format=iife can be passed when compressing, so that global variables will not be exposed, but it also causes conflicts with built-in variables such as module and require.

Well, I'm not sure if esbuild needs to solve this problem, after all this is used across compilation tools, and I have other ways to solve my problem now. If it is not a problem, you can close this issue directly.

image image

peakchen90 avatar Dec 30 '23 05:12 peakchen90