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

With target=chrome99, code with var is generated #2267

Closed
gkatsanos opened this issue May 26, 2022 · 7 comments
Closed

With target=chrome99, code with var is generated #2267

gkatsanos opened this issue May 26, 2022 · 7 comments

Comments

@gkatsanos
Copy link

gkatsanos commented May 26, 2022

I'm pretty sure this is 'expected behavior' but I don't get it, as the documentation clearly states:

The default target is esnext which means that by default, esbuild will assume all of the latest JavaScript and CSS features are supported

// input file
if (module.hot) {
    module.hot.accept();
}


document.title = 'Hello World';

setTimeout(() => {
    document.title = 'Hello World 3';
}, 1000)


const a = 1;

const posts = [
    {
        title: 'Post One',
        body: 'This is post one'
    },
    {
        title: 'Post Two',
        body: 'This is post two',
    }
]


const morePosts = [
    ...posts,
    {
        title: 'Post Three',
        body: 'This is post three'
    }
]

document.body.innerHTML = morePosts
yarn esbuild --bundle --target=chrome99 src/somescript.js --outfile=esbuild-dist/index.js
// result
(() => {
  var __getOwnPropNames = Object.getOwnPropertyNames;
  var __commonJS = (cb, mod) => function __require() {
    return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
  };

  // src/somescript.js
  var require_somescript = __commonJS({
    "src/somescript.js"(exports, module) {
      if (module.hot) {
        module.hot.accept();
      }
      document.title = "Hello World";
      setTimeout(() => {
        document.title = "Hello World 3";
      }, 1e3);
      var posts = [
        {
          title: "Post One",
          body: "This is post one"
        },
        {
          title: "Post Two",
          body: "This is post two"
        }
      ];
      var morePosts = [
        ...posts,
        {
          title: "Post Three",
          body: "This is post three"
        }
      ];
      document.body.innerHTML = morePosts;
    }
  });
  require_somescript();
})();
@gkatsanos gkatsanos changed the title Even though target is chrome99, ES5 code is generated With target=chrome99, code with var is generated May 26, 2022
@evanw
Copy link
Owner

evanw commented May 26, 2022

One reason I’m using var is because let has a performance cost over var due to something called the “temporal dead zone.” In some cases the performance cost can be pretty extreme: #478.

Another reason is because sometimes bundling needs to lazily-initialize a module and, when that module is in ESM format, the top-level symbols are separated from their initializers. This means const variables won’t work (since const requires an initializer). Because of esbuild’s speed-optimized architecture (and to simplify the code), this transformation is just always done instead of only being done when needed so that the linker doesn’t need to do complex code rewriting (class declarations are the most complicated to transform, and are also affected because they also have a temporal dead zone).

Note that when esbuild turns const into var during bundling, it prevents assignments to const variables at compile time, so it’s still not possible to mutate a const variable.

@gkatsanos
Copy link
Author

gkatsanos commented May 27, 2022

First of all thanks for the detailed reply! I actually thought that was because of a config issue on my end. (Maybe something to add as a sidenote in the docs). Its pretty surprising to hear let might have a performance cost over var (a bit disappointing), but I trust your analysis! (is this V8 or browser related, something to escalate?)
Just to double-check: I did some follow-up experiments and it seems indeed all instances of const are replaced with var regardless of how/where they appear.
I run a small comparison of parcel/esbuild just for my own understanding, here's both of them, minified / same target:

// esbuild
(()=>{var e=(s,o)=>()=>(o||s((o={exports:{}}).exports,o),o.exports);var h=e((r,t)=>{t.hot&&t.hot.accept();document.title="Hello World";setTimeout(()=>{document.title="Hello World 3"},1e3);var i=1,l=2,n=i+l;console.log(n);var c=[{title:"Post One",body:"This is post one"},{title:"Post Two",body:"This is post two"}],d=[...c,{title:"Post Three",body:"This is post three"}];document.body.innerHTML=d});h();})();
// looking at the unminified version of this above, seems that it's converting it to a CJS module? did I get this right?
// parcel
document.title="Hello World",setTimeout((()=>{document.title="Hello World 3"}),1e3);console.log(3);const o=[{title:"Post One",body:"This is post one"},{title:"Post Two",body:"This is post two"},{title:"Post Three",body:"This is post three"}];document.body.innerHTML=o;
//# sourceMappingURL=index.cee23c50.js.map

Follow-up question: any other features that are 'transpiled' on purpose? @evanw consider my initial question answered but would love a follow-up reply :)

@gkatsanos gkatsanos reopened this May 30, 2022
@evanw
Copy link
Owner

evanw commented Jun 8, 2022

More info regarding your initial question: Another reason is minification. Changing const into let and sometimes also let into var allows all variable declarations to merge together, which results in smaller code. Declaring variables using more similar keywords also likely helps improve gzip compression somewhat.

Follow-up question: any other features that are 'transpiled' on purpose?

Yes. I mentioned class declarations briefly above already but they are another case that's very similar to const and let. These also have temporal dead zone issues (which can cause performance problems) and also have issues with lazily-initialized modules. So esbuild does this transformation for top-level class declarations for the same reason:

// Before
class Foo {}

// After
var Foo = class {};

As a side effect of this transformation of class declarations, static fields may also be lowered. This is a known esbuild limitation, and is described in more detail in this comment. This is mostly irrelevant for normal static fields but it can result in lower performance of #private fields: #1328.

There are also some other more obscure cases. One I can think of is that esbuild automatically escapes </script> when it's present in the output file in case the output file is later embedded in HTML. If esbuild didn't do this, then the browser would truncate the output file at the closing script tag, which would cause a syntax error and also trash the HTML page with the remaining JavaScript code. This mostly just means changing </script> into <\/script> in string literals except in one case: tagged template literals. Long story short String.raw`</script>` !== String.raw`<\/script>` in JavaScript so in this case esbuild transpiles the template literal into a form where the slash can be escaped even when template literals are supported:

// Before
console.log(String.raw`</script>`);

// After
var __freeze = Object.freeze;
var __defProp = Object.defineProperty;
var __template = (cooked, raw) => __freeze(__defProp(cooked, "raw", { value: __freeze(raw || cooked.slice()) }));
var _a;
console.log(String.raw(_a || (_a = __template(["<\/script>"]))));

There may be other cases, although I can't think of any more right now.

@evanw
Copy link
Owner

evanw commented Jun 15, 2022

I'm closing this issue because there is nothing to do here, and this behavior is intentional.

@evanw evanw closed this as completed Jun 15, 2022
@whoami-anoint
Copy link

Check it out: Why shouldn't we use var in JavaScript

@lazarljubenovic
Copy link

That article is talking about authoring code using var, not about VM running it.

@MauricioAndrades
Copy link

MauricioAndrades commented Jun 24, 2023

Check it out: Why shouldn't we use var in JavaScript

This article is terrible, how terrible? Enough for me to write the following:

In the first example under "Lack of Encapsulation," he mentions that variables declared with var are accessible from anywhere within the function, but the code demonstrates accessing the variable outside the function. It would be more accurate to say that var variables are accessible from anywhere within the enclosing function scope.

In the second example about hoisting, the code tries to access myVariable before it is declared, which results in undefined. However, he mentions that variable declarations using var are hoisted, but it does not cover the fact that only the declaration is hoisted, not the assignment.

The fourth example about variable overriding correctly demonstrates redeclaring a variable within the same scope. However, he states that redeclaring a variable with the same name within the same scope "simply reassigns its value, without throwing an error." While reassignment occurs, it does not technically "redeclare" the variable.

The fifth example about no block-level declaration shows a loop where the variable i is declared with var. The code correctly demonstrates that i is accessible outside the loop. However, he states that var lacks block-level scoping, making it harder to manage variables within specific blocks. While this is true, the example itself does not showcase the problem.

omar-azmi added a commit to oazmi/esbuild-generic-loader that referenced this issue Dec 1, 2024
- fix an incompatibility-issue due to esbuild's output with `String.raw` when a `"</script>"` substring is present in it.
  - esbuild would create a runtime function that specifically escapes the said substring.
    however, if multiple entrypoints are used with code-splitting enabled, then this function is imported from a common file.
    but as we know, our virtual javascript-code inside of the `unparseFromJs` method cannot perform an import.
  - the workaround is to escape any `"</script>"` substring ourselved, so that esbuild will not generate that runtime code.
  - see the following esbuild issue comment for more details: evanw/esbuild#2267 (comment)
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

5 participants