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

Migrate package/source to ES module(s) #537

Merged
merged 17 commits into from Sep 9, 2019
Merged

Migrate package/source to ES module(s) #537

merged 17 commits into from Sep 9, 2019

Conversation

carhartl
Copy link
Member

@carhartl carhartl commented Sep 6, 2019

This is the first part (foundation) of the effort to modernize the package. We start providing the package as an ES module.

While the package's main remains a pointer to a CommonJS UMD module we're also providing the ES module variant via the package's module property, so that bundlers are aware of it.

Syntax hasn't been modernized yet (out of scope of this PR); it will require adding Babel to the Rollup.js based build tooling for browser compatibility. Similarly, only in a follow-up PR I'm going to provide an automated release process, so we can produce the required distributions in a fail-safe manner.

(Along the way I've updated QUnit "accidentally", this wasn't strictly required for this PR.)

  • Turn source into ES module
  • Adapt build tooling to produce nomodule/module variants
  • Adapt tests to include testing the module
  • Adapt documentation
  • Adapt package.json (include main for commonjs/nomodule variant, and module, distributing minified versions via files would take care of Please provide a compressed package of es6's module syntax in NPM. #501)
  • Remove support for bower
  • Drop support for noConflict()

Notes:

See #528

Closes #536, #535, #423

@carhartl carhartl added this to the v3.0.0 milestone Sep 6, 2019
@carhartl carhartl self-assigned this Sep 6, 2019
@carhartl
Copy link
Member Author

carhartl commented Sep 6, 2019

include main for commonjs/nomodule variant, and module

Both variants should sit in a dist directory, for this to ensure correctness we'd need to have an automated release process!

https://www.npmjs.com/package/release-it

EDIT: Once this is in place (separate PR!) we can close #501.

@carhartl carhartl force-pushed the es-module branch 3 times, most recently from 449ead4 to 7d5fad2 Compare September 7, 2019 12:28
As amd + umd compatible modules are now being build by rollup we
shouldn't need to test this any longer.
We need to reference the rollup umd output file here.
@carhartl carhartl marked this pull request as ready for review September 8, 2019 09:58
@carhartl
Copy link
Member Author

carhartl commented Sep 8, 2019

@mkay581 Do you want to take a look?

README.md Outdated Show resolved Hide resolved
We produce variants of the source code by rollup. Note using a "module"
property is not (yet?) standard but the approach both webpack and
rollup.js suggest going forward.
Rollup provides an out-of-the-box option for this, thus not going to
test (generated).
@carhartl carhartl force-pushed the es-module branch 2 times, most recently from 06ab811 to b243a44 Compare September 8, 2019 12:18
Copy link

@markcellus markcellus left a comment

Choose a reason for hiding this comment

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

I didn't look too deeply at the internal code but this overall looks good! I had a comment about the mjs file extension. Nice work!


```html
<script src="https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js"></script>
<script type="module" src="/path/to/js.cookie.mjs"></script>

Choose a reason for hiding this comment

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

Hmmm enforcing consumers to use a JS file with a .mjs file extension at this level seems a little odd. Would all browsers know how to interpret a .mjs file extension?

Copy link
Member Author

Choose a reason for hiding this comment

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

I didn't mean to force consumers, more like provide an example of how to include the module of that package directly, along with the fallback. In general, browsers do not care about the extension, though it's important a server serves mjs files with the proper mime type of text/javascript.

Generally I'm assuming the more likely use case for ES modules is to be bundled up, so I might add that here...

Copy link
Member Author

Choose a reason for hiding this comment

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

Or do you mean it's too early to use mjs files in general? I was looking at https://v8.dev/features/modules and it seemed the way to go..

Copy link

@markcellus markcellus Sep 9, 2019

Choose a reason for hiding this comment

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

AFAIK, mjs files only work with node out of the box. The only way browsers would parse a file with this extension is if, as you say, the

server serves mjs files with the proper mime type of text/javascript

In other words, this won't work for consumers if they don't know to configure their server to serve the proper mime type. I would suggest we change the .mjs extension to .js like mentioned in #537 (comment). Then it wouldn't require a server change.

Copy link
Member

Choose a reason for hiding this comment

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

It seems the value for outputting a .mjs extension is to assist on documentation. Should we stick with one file for simplicity? Is there another reason for having both .js and .mjs?

Copy link
Member Author

Choose a reason for hiding this comment

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

For now I'm siding with the node developers and wish to keep the extension:

Still, we recommend using the .mjs extension for modules, for two reasons:

  1. During development, the .mjs extension makes it crystal clear to you and anyone else looking at your project that the file is a module as opposed to a classic script. (It’s not always possible to tell just by looking at the code.) As mentioned before, modules are treated differently than classic scripts, so the difference is hugely important!
  2. It ensures that your file is parsed as a module by runtimes such as Node.js and d8, and build tools such as Babel. While these environments and tools each have proprietary ways via configuration to interpret files with other extensions as modules, the .mjs extension is the cross-compatible way to ensure that files are treated as modules.

I'm assuming it will be rather rare that the source will be consumed directly, rather than being bundled up with a tool. In the latter case the extension matters less, stressing the aforementioned arguments further.

Let's see how it goes. We can always change that back later on, but let's get some feedback first and start releasing a v3 beta once we're ready...

Copy link
Member

Choose a reason for hiding this comment

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

Sounds good, if it's a convention it doesn't hurt simply following it.

@@ -0,0 +1,143 @@
/*!

Choose a reason for hiding this comment

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

can this file just be named src/js.cookie.js?

Copy link
Member Author

Choose a reason for hiding this comment

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

See my comment in the other discussion.. for now I'd like to keep it as it is, and be very explicit about this being an ES module.


```html
<script src="https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js"></script>
<script type="module" src="/path/to/js.cookie.mjs"></script>
Copy link

@markcellus markcellus Sep 9, 2019

Choose a reason for hiding this comment

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

AFAIK, mjs files only work with node out of the box. The only way browsers would parse a file with this extension is if, as you say, the

server serves mjs files with the proper mime type of text/javascript

In other words, this won't work for consumers if they don't know to configure their server to serve the proper mime type. I would suggest we change the .mjs extension to .js like mentioned in #537 (comment). Then it wouldn't require a server change.

@FagnerMartinsBrack
Copy link
Member

Syntax hasn't been modernized yet (out of scope of this PR)

What do you mean by this part?

@carhartl
Copy link
Member Author

carhartl commented Sep 9, 2019

@FagnerMartinsBrack

What do you mean by this part?

I was referring to that we're not (yet) using modern ES syntax at all, but I wish to start doing so. E.g. arrow functions, const etc.

This PR provides the foundation for that (Rollup.js), we'd need an additional babel transpilation step in order to maintain compatibility with some older browsers I assume (not sure whether IE 10 already supports these newer syntax features).

Or we're going for TypeScript, which by now I'm open to.. 😬

@FagnerMartinsBrack
Copy link
Member

FagnerMartinsBrack commented Sep 9, 2019

@carhartl

I believe TypeScript is an unnecessary build complexity for almost no gain in js-cookie. It's not like we have tons of modules calling each other where an interface becomes strictly necessary to ensure program correctness. Our test suite is pretty thorough, it's not like TypeScript is gonna save us from bugs.

Instead of relying on babel to transpile the code, what if we make sure that we run the tests on every supported browser to make sure we're not using unsupported features (Browserstack)? That way we avoid adding more build steps, we won't need them. That also allows us to focus on reducing the size of the lib by working directly on the code we control instead of magically generating code we don't control.

The only build steps or tools I see value here without an unnecessary level of complexity are the test runs, the minification step, and the byte size check.

What do you think?

@carhartl
Copy link
Member Author

carhartl commented Sep 9, 2019

Instead of relying on babel to transpile the code, what if we make sure that we run the tests on every supported browser to make sure we're not using unsupported features (Browserstack)? That way we avoid adding more build steps, we won't need them. That also allows us to focus on reducing the size of the lib by working directly on the code we control instead of magically generating code we don't control.

I agree with all of that. At the same time, though, using the very same talking points, you could argue that this whole PR is not needed. I believe it's worth it starting to write JS code in a more modern fashion.

@carhartl carhartl merged commit 98325f8 into master Sep 9, 2019
@carhartl carhartl deleted the es-module branch September 9, 2019 15:58
@carhartl
Copy link
Member Author

carhartl commented Sep 9, 2019

Also, using newer syntax idioms will likely produce smaller sizes. It really depends on which browsers we need to target though. I haven't thought that through yet.

@FagnerMartinsBrack
Copy link
Member

Can't we use the modern syntax straight away now that IE support is gone?

@carhartl
Copy link
Member Author

carhartl commented Sep 9, 2019

Can't we use the modern syntax straight away now that IE support is gone?

Not as long as we’re supporting IE 10 + 11:
https://caniuse.com/#search=%3D%3E

@FagnerMartinsBrack
Copy link
Member

I see. From what I looked at the code of seems we still have both options of using the js file or the ES6 one so ppl can still choose only modern code.

Also might need to notify jsdelivr about which bundle is the suportted one, it seems they're minifying themselves when a new js-cookie comes out since we don't have minified version in the SCM.

@carhartl
Copy link
Member Author

carhartl commented Sep 9, 2019

..if I find some more time I’m going to experiment with this, in order to see how much bigger a transpiled source would become. Maybe we can save somewhere else, using for instance forEach() and stay below the 900 bytes mark.

@carhartl
Copy link
Member Author

carhartl commented Sep 11, 2019

Started a small fun experiment with the syntax for the extend function...

At the moment it looks like this:

function extend () {
  var i = 0
  var result = {}
  for (; i < arguments.length; i++) {
    var attributes = arguments[i]
    for (var key in attributes) {
      result[key] = attributes[key]
    }
  }
  return result
}

Let's use some of the newer Array methods, without the need for transpiling, it's all still supported by IE 10+. This doesn't look much smaller though, and also not necessarily more readable, so maybe there's not much to gain:

function extend () {
  return [].slice.call(arguments).reduce(function (a, c) {
    Object.keys(c).forEach(function (k) {
      a[k] = c[k]
    })
    return a
  })
}

Let's start using arrow functions! This is starting to look better:

function extend () {
  return [].slice
    .call(arguments)
    .reduce((a, c) => (Object.keys(c).forEach(k => (a[k] = c[k])), a))
}

Still we have to deal with arguments. Since we're now in transpile land, why not use Array.from? This is starting to look better:

function extend () {
  return Array.from(arguments)
    .reduce((a, c) => (Object.keys(c).forEach(k => (a[k] = c[k])), a))
}

Still too much code though, let others do all the heavy-lifting:

function extend () {
  return Object.assign({}, ...arguments)
}

So yeah, at this point the extend function is a bit pointless (note that Babel would transpile this adding a polyfill for Object.assign()):

    attributes = Object.assign(
      {
        path: '/'
      },
      api.defaults,
      attributes
    )

@FagnerMartinsBrack
Copy link
Member

FagnerMartinsBrack commented Sep 11, 2019

yeah, I think we built the extend() function at the time for the very reason Object.assign wasn't supported by the browsers using js-cookie

@carhartl
Copy link
Member Author

Maybe it didn't even exist, I remember when we were "unjqueryfying" the source we needed a replacement for $.extend :)

I was curious about a thing. In a bundling scenario I could imagine we were actually saving bytes, at least when other packages were also requiring the same polyfill, the bundler should only add it once... (?)

@FagnerMartinsBrack
Copy link
Member

FagnerMartinsBrack commented Sep 12, 2019

We would be actually saving bytes in a bundling scenario if we were still using $.extend. However, that would mean we still would have a dependency on jQuery and we would be adding more bytes for people who don't use it.

I believe the path that was taken so far is to reduce the number of dependencies required for developers using js-cookie other than on the ECMAScript spec. Now with React, Vue, etc. I see that decision as making even more sense.

When React hype goes by, JSX becomes an HTTP media type, or something else comes along, js-cookie will still survive.

It will only die once cookies are no more.

That's why I'm very weary on adding too many dependencies to it, although dev dependencies are easy to pivot so it's ok.

@carhartl
Copy link
Member Author

I think you misunderstood me. I wasn’t advocating for bringing back a dependency to jQuery, nor any other dependency.

(And in codebases where there was a polyfill for Object.assign() anyway, we would bring in less code, net, if we were relying on it, instead of providing a (redundant) extend implementation.)

@FagnerMartinsBrack
Copy link
Member

FagnerMartinsBrack commented Sep 12, 2019

Oh ok, you were talking about a polyfill to Object.assign() not $.assign, haha, now I got it.
Yeah, it could make sense if we polyfilled it... who knows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Migrate to ES modules
3 participants