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

Add build setup (ES5 & ES6, minified & unminified) #33

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

jhildenbiddle
Copy link

@jhildenbiddle jhildenbiddle commented Apr 30, 2021

fix #32

  • Convert source to ESM
  • Add build process to generate ES5 & ES6 distributable files (minified and unminified)
  • Update fields in package.json to reference distributable files (main, module, browser, and unpkg)
  • Add files field in package.json to remove source and include /dist files in package published to npm
  • Update .gitignore to accommodate changes
  • Add node 16 to CI test matrix
  • Update documentation

The end results is a new /dist directory with the following files:

  • balanced-match.esm.js
  • balanced-match.esm.js.map
  • balanced-match.esm.min.js
  • balanced-match.esm.min.js.map
  • balanced-match.js
  • balanced-match.js.map
  • balanced-match.min.js
  • balanced-match.min.js.map

These eight files provide support for legacy and modern browser and node environments.

@@ -1,4 +1,4 @@
const balanced = require('./')
const balanced = require('./dist/balanced-match.js')
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

node comes with native support in recent versions, I wonder if we need to add a bundler, or can just bump the major and make it an ESM only module, as @sindresorhus has recently done with many modules (eg sindresorhus/p-queue@8c7325a)

Copy link
Author

@jhildenbiddle jhildenbiddle May 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's certainly an option and sindresorhus has made a case for doing so on his blog, but I don't think moving to ESM only is the right decision for every project. For server-side libraries, sure. For projects that may run in a browser, it makes sense to continue offering an ES5 version a bit longer.

A few issues I think are worth considering:

  1. The package you linked to (p-queue) is better positioned to go "ESM only" because the library requires Promise support. Practically speaking, this means that if the library is run in a browser it will almost certainly do so in environments that support ESM modules.

  2. Is this library able to run client-side? If so, then I believe the library should offer legacy-compatible code at least until MS officially ends support for IE11 (August 17, 2021).

    The alternative is to force library consumers to wrestle with webpack, rollup, parcel, etc. Even if these tools are already being used in a project, they have to be (re)configured to transpile some-but-not-all dependencies in /node_modules to avoid accidentally accidentally shipping ES6 code in ES5 bundles when they update to ESM-only versions of a library. This is what happened to me when updating balance-match to v2 in css-vars-ponyfill: I was already using babel, but it was configured to ignore files in /node_modules. As a result, rollup+babel created an ES5 bundle that contained properly transpired code (my source) and untranspiled ES6 code (balance-match v2). See v2.4.4 no longer transpiles to pure ECMAScript 5 jhildenbiddle/css-vars-ponyfill#158 for details.

  3. Is this a library consumers may want to load from a CDN? If so, then I believe the library should offer minified as well as unminifed versions:

    <!-- Minified UMD -->
    <script src="https://unpkg.com/balanced-match@3"></script>
    <!-- Minified ESM -->
    <script type="module" src="https://unpkg.com/balanced-match@3/dist/balances-match.esm.min.js"></script>

Just my $0.02. :)

If you prefer going "ESM only", I'm happy to made those changes and update the docs accordingly. Given that previous versions of balanced-match were written in ES5, I think it's important to let consumers know that the current version requires transpilation if used in legacy environments.

Copy link
Author

@jhildenbiddle jhildenbiddle May 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @juliangruber.

To add some context for the desire to provide an ES5-friendly version: css-vars-ponyfill is a library I maintain that is used primarily to make life easier for projects that are forced to support IE11. It is downloaded over 100k per week from npm and over 110m per month from just one CDN (jsdelivr). As devs we're all anxious to move past legacy browsers, but not everyone has the luxury to do so.

Happy to make changes if requested.

Thanks!

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For server-side libraries, sure. For projects that may run in a browser, it makes sense to continue offering an ES5 version a bit longer.

Interesting, in my experience it's the opposite. All of my client side projects are transpiled, none of my server side projects are, so I've been using ESM in the browser for a while. Is it the opposite for you?

If you were to consume and not have a bundler set up, how would you do it? Is that a realistic use case for this library?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that for "entry point" libraries, like jQuery this makes sense, but for this library I anticipate it will be consumed in conjunction with others, where then using a bundler makes a lot more sense.

Thank you for all this work on this so far, however I'm not convinced this is the right way, and am still in favor of going ESM only (if anything).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I asked for community input in https://twitter.com/juliangruber/status/1391336252096516098

The poll is interesting, but it misses the point of the PR. A better poll would be "should library authors pre-transpile distributable files to ES5 or are library consumers responsible for transpiling their dependencies to ES5?" I would expect the answer to be different for new libraries that have never offered ES5 versions versus well-established libraries that have always been ES5.

I think in 2021 there is a strong argument for moving to ES modules only, but the code in that module should still be ES5 if there is a reasonable chance it will be run in a legacy browser. Give the age and popularity of this library, I think that is a reasonable assumption.

Again, just my $0.02. 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we should just move to ESM

@juliangruber
Copy link
Owner

Thanks for all this input, I'll get to it next week 👏 🙇

@jhildenbiddle
Copy link
Author

jhildenbiddle commented May 9, 2021

@juliangruber --

All of my client side projects are transpiled, none of my server side projects are

Yep. Same for me. Transpiling to ES5 for node isn't (usually) the concern. The browser is where I'm focused.

...so I've been using ESM in the browser for a while.

Which means you're targeting modern browsers and won't run into the issue I'm describing. I think that might be where we're getting our signals crossed. A mixture of ES5 and ES6 code won't break modern browsers that can handle both, but it will break legacy browsers that can't handle ES6.

... for this library I anticipate it will be consumed in conjunction with others, where then using a bundler makes a lot more sense.

That's exactly how I consume this library in css-vars-ponyfill, and the change from ES5 to ES6-only caused me to release a broken ES5 bundle. The same will happen to any other project that depends on balanced-match that fails to transpile their dependencies (which they shouldn't have to, IMHO) if they support legacy browsers.

Consider these two versions of balanced-match and how they impact css-vars-ponyfill:

  • v1.0.2 of balance-match was a CommonJS module written in ES5
  • v2.0.0 of balance-match is a CommonJS module written in ES6 (use of const and let)

My transpilation configuration (almost) always ignores files in node_modules because the expectation is that distributable libraries have already been transpiled to ES5. This expectation exists for two reasons: 1) babel shouldn't have to process dependencies in /node_modules because this will impact build times, and 2) devs shouldn't have to manually track and configure babel to transpile individual dependencies in node_modules because doing so incorrectly will (probably) break things.

Using v1.0.2 with babel configured to transpile only my /src/ directory, my legacy-compatible (ES5) bundle contains all ES5 code as expected. This is because all dependencies in /node_modules (including balanced-match) are written in ES5. Great!

Using v2.0.0 with the same babel configuration, my legacy-compatible bundle contains ES5 code (transpiled from /src/) and ES6 code (untranspiled from /node_modules/balanced_match). This is because balanced-match contains ES6 code that babel did not transpile. My legacy bundle now breaks in legacy browsers. Not great. Now I'm forced to update my babel configuration so balanced match is transpiled properly with one of the two bad options listed above.

If you were to consume and not have a bundler set up, how would you do it? Is that a realistic use case for this library?

Via a CDN, as an ES module for modern browsers or an IIFE/UMD for legacy browsers. Is this less likely that consuming via bundler? Probably, but why not let people consume the library however they see fit?

But just to be clear, this PR isn't about generating different versions of balanced-match so it can be consumed via a CDN. That was just a bonus that came from implementing a bundler which made it easy to do. The real purpose of this PR is to ensure that balanced-match offers an ES5 distributable. Notice that the /dist folder created after running npm run build with this PR contains only ES5 code, so even if a project bundles the ES modules (as mine does via rollup), the bundle will contain ES5 code.

Hope this helps explain bit better. Apologies for the lengthy response.

@juliangruber
Copy link
Owner

Which means you're targeting modern browsers and won't run into the issue I'm describing. I think that might be where we're getting our signals crossed. A mixture of ES5 and ES6 code won't break modern browsers that can handle both, but it will break legacy browsers that can't handle ES6.

You can transpile ES6 code to ES5 code for browsers, so import isn't a deal breaker 🤔

@jhildenbiddle
Copy link
Author

jhildenbiddle commented May 10, 2021

You can transpile ES6 code to ES5 code for browsers, so import isn't a deal breaker 🤔

Yep. I just propose that you (the library author) should do the transpilation to ES5 so your library consumers don't have to. Why? For all of the reasons stated above, but summarized because it is much easier for you to do so and the cost to you is near zero.

The goal of this PR is to support all platforms (legacy and modern, node and browser), allow consumers to use the library however they see fit (import, require, or <script>), and not break dependent projects (like mine).

With v1, consumers could do this:

// ES5 CJS ("main" in package.json)
const balanced = require('balanced-match');

With v2, consumers can do this:

// ES6 CJS ("main" in package.json)
// 👎 Legacy bundles now require transpilation of node_modules  
const balanced = require('balanced-match');

With this PR, consumers can do this:

<!-- ES5 ESM ("module" in package.json) -->
<script type="module" src="http://my.cdn.com/balanced-match/dist/balanced-match.esm.min.js"></script>

<!-- ES5 UMD ("browser" in package.json) -->
<script src="http://my.cdn.com/balanced-match/dist/balanced-match.min.js"></script>
// ES5 ESM ("module" in package.json)
import balanced from 'balanced-match';

// ES5 UMD ("main" value in package.json)
const balanced = require('balanced-match');

The main criticism of the approach above is that modern platforms are forced to use the (potentially) larger transpiled ES5 source. That is not an issue for the particular library though as the ES5 and ES6 versions are roughly the same size. So, why not just ship ES5 everywhere and make life easier for consumers?

The other logical option is to deliver ES6 code as an ES module and ES5 code as a UMD. The advantage to this is that ES6-capable platforms receive ES6 code. The disadvantage is that dependents that bundle code for legacy browsers will either have to 1) require balanced-match to ensure they are getting ES5 code, or 2) import and transpile the balanced-match source in /node_modules. Since balanced-match is only offered as a CommonJS module today, this isn't really a big deal.

// ES6 ESM ("module" in package.json)
// 👍 Modern platforms get ES6 code
// ⚠️ Legacy bundles require transpilation of node_modules when using `import`
import balanced from 'balanced-match';

// ES5 UMD ("main" value in package.json)
// 👍 No transpilation necessary for legacy bundles
// 👎 Modern platforms get ES5 code
const balanced = require('balanced-match');

Either options seems like a better alternative to the current "ES6 as CJS" approach used in balanced-match v2.x

Anyway, this is a lot of back-and-forth for what I thought would be a pretty straight-forward PR. Happy to continue the discussion if you like or move on if you feel strongly about leaving things as-is.

Thanks!

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

Successfully merging this pull request may close these issues.

Add separate ES5 & ES6 builds & update package.json
3 participants