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

Babel targets configuration for browser and node builds #982

Open
make-github-pseudonymous-again opened this issue Jul 25, 2022 · 4 comments

Comments

@make-github-pseudonymous-again

For a while I have been using the following babel preset for production bundles:

        ...
        "presets": [
          [
            "@babel/preset-env",
            {
              "targets": [
                "defaults",
                "maintained node versions"
              ]
            }
          ]
        ],
        ...

This configuration seems to be incompatible with microbundle as it generates errors such as unknown Statement of type "ForOfStatement" when the source contains for (... of ...) statements. Using "presets": ["@babel/preset-env"] without specifying targets works. However, I am concerned about the compatibility achieved by such "default" builds.

What is the proper way to use microbundle to build for both the browser and for node? What's the correct babel configuration to use? I am fine with having one build for the browser and another for Node, but how should one define package.json exports in that case?

I do realize microbundle probably does the correct thing by default, but I am a bit skeptical given the existence of the --target CLI flag, for which little documentation exists.

@multivoltage
Copy link
Contributor

Mayne check my previus question

@make-github-pseudonymous-again
Copy link
Author

make-github-pseudonymous-again commented Jul 27, 2022

Thanks @multivoltage. I understand that microbundle's goal is to produce bundles that are as small as possible. I am interested in small bundles, but I am even more interested in compatibility with the most popular execution environments for ESM: reasonably popular web browsers versions and maintained node versions. I see two approaches to solve this problem:

  1. Produce a single batch of bundles that are compatible with all targets. The package.json conditional exports are then trivial to implement (.module.js output for module key, .cjs for main and exports.require, and .modern.js for exports.default).
  2. Produce two batches of bundles, one with --target web (default) and one with --target node. But then it is not clear how to map the different keys.

I am trying to do 1, but I hit some sort of babel misconfiguration. I have no idea on how to do 2 so that it works reliably. Sure modern node and webpack (and also rollup?) support require and import. But the condition node does not seem to have a way to distinguish between CommonJS and ESM. It looks like for it to work we would need conditions node:require, node:import, browser:require, and browser:import. Maybe someone knows better?

PS: Do I understand correctly from the webpack docs that to compose conditionals one can cascade them?

@make-github-pseudonymous-again
Copy link
Author

It seems the solution is to give up on custom configurations and let microbundle decide. microbundle bears the responsibility of setting the correct configuration to obtain correct builds for contemporary targets. If you want a build compatible with both contemporary Node and Web, use --target web (the default). If you really want an unmangled build for Node, build twice, once with --target web, and once with --target node. Then use the following configuration in package.json:

  ...
  "type": "module",
  "source": "src/foo.js",             // your source code
  "exports": {
    "node": {
      "require": "./dist/node/foo.cjs",
      "default": "./dist/node/foo.modern.js",
    },
    "default": "./dist/web/foo.modern.js"
  },
  "main": "./dist/node/foo.cjs",           // where to generate the CommonJS bundle
  "module": "./dist/web/foo.js",   // where to generate the ESM bundle
  ...

The reason my example usage crashes is that microbundle esm and cjs formats force the transpilation of generator functions and for ... of syntax but when combined with the @babel/preset-env configuration above, somehow only the generator functions are transpiled, but not the for ... of syntax. Related issues:

Ideally, I think there should be a tool similar to microbundle but that forces explicit babel configuration without creating these conflicts. It would not be that much more of a hassle to setup if microbundle defined their default babel configuration for the combinations of builds and targets as independent babel presets. Would microbundle still add value compared to direct use of rollup in this case? I think so: handles TypeScript by default, produces tiny code, automatic build/target detection (via main, module, and exports). Arguably these need less custom configuration.

@make-github-pseudonymous-again
Copy link
Author

make-github-pseudonymous-again commented Sep 3, 2022

Another solution seems to be to explicitly rely on @babel/plugin-transform-for-of, for instance:

...
"presets": [
  [
    "@babel/preset-env",
    {
      "targets": [
        "defaults",
        "maintained node versions"
      ]
    }
  ]
],
"plugins": [
  "@babel/plugin-transform-for-of"
]
...

PS: And if you use destructuring, @babel/plugin-transform-destructuring.

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

2 participants