developit / microbundle

📦 Zero-configuration bundler for tiny modules.
https://npm.im/microbundle
MIT License
8.06k stars 362 forks source link

--format=modern still converts modern JS to old-style JS? #775

Open Pomax opened 3 years ago

Pomax commented 3 years ago

I was trying to figure out why my code was doing the wrong thing, and ran into the fact that using --format=modern still seems to yield legacy JS:

> microbundle watch -i ./src/react-app/ui.js -o ./public/preact-ui.js --format=modern --no-sourcemap --no-compress

turned the following:

class MJUI extends Component {
  constructor(props) {
    // ...
  }

  renderGames(state) {
    return state.games.map((g) => {
      if (g.finished) return;

      let joinOrStart = async () => {
        // Note that these two operations are mutually
        // exclusive, because owner's can't join, and
        // non-owners can't start a game:
        this.server.game.join(g.name);
        this.server.game.start(g.name);
      };

      // ...
    });
  }

  // ...
}

into:

  // ...

  renderGames(state) {
    var _this = this;

    return state.games.map(g => {
      if (g.finished) return;

      let joinOrStart = async function joinOrStart() {
        // Note that these two operations are mutually
        // exclusive, because owner's can't join, and
        // non-owners can't start a game:
        _this.server.game.join(g.name);

        _this.server.game.start(g.name);
      };

  // ...

Note that the filename has the .modern.js suffix, so something feels rather off?

developit commented 3 years ago

This is actually correct - the original code causes an unrecoverable error when renderGames() is called in Safari 10 and 11 (there is a bug relating to async arrow functions in classes). Microbundle uses preset-env's bugfixes option to output code that supports all browsers that implement <script type="module"> (Chrome 61+, Firefox 60+, Safari 10.3+, Edge 16+). The result is that it converts the async arrow function to an async function, which is much faster and smaller than transpiling the async/await to Regenerator.

Pomax commented 3 years ago

Hm, how do I tell it that I couldn't care less about browsers that don't support the modern web, so that nothing gets rewritten? No need for an async/await regenerator, just leave the code alone, it's already modern JS supported by everything including Node 15, except, apparently, Safari? (because Safari's notoriously the new IE in this arena)

Pomax commented 3 years ago

(and happy new year, of course =)

developit commented 3 years ago

For that you'd want to ignore the "modern" output format and use the "esm" format, then specify your support target using Browserslist:

// package.json
{
  "browserslist": [
    "last 2 years",
    "node >= 12"
  ]
}

The modern output format is pretty specifically designed to generate ES2017 code because that offers 95-96% browser support, whereas newer syntax levels are sub-90%.

Pomax commented 3 years ago

Hmm, I made those changes but I can't seem to get it to leave the code alone. I figured I'd set it to purely chrome, to see if that'd yield the least amount of rewriting:

  "browserslist": [
    "last 2 Chrome versions"
  ]

but even then, the code gets fairly drastically rewritten instead of leaving the modern JS in place:

renderGames(state) {
    return (state.games || []).map(g => {
      const _this2 = this;

      if (g.finished) return;

      let joinOrStart = function () {
        try {
          // Note that these two operations are mutually
          // exclusive, because owner's can't join, and
          // non-owners can't start a game:
          _this2.server.game.join(g.name);

          _this2.server.game.start(g.name);

          return Promise.resolve();
        } catch (e) {
          return Promise.reject(e);
        }
      };

(Also on a potential PR note, the CLI flags currently say -f, --format Only build specified formats (any of modern,es,cjs,umd or iife) (default modern,es,cjs,umd) using an es format rather than esm format - it looks like both work, generating a file.esm.js output; does it make sense to update the readme/help output to say esm so it matches the file that gets generated?)