Skip to content

Convert to ES modules, improve encapsulation#725

Open
VanessaPhippsCfa wants to merge 64 commits intoableplayer:developfrom
Smithsonian:develop
Open

Convert to ES modules, improve encapsulation#725
VanessaPhippsCfa wants to merge 64 commits intoableplayer:developfrom
Smithsonian:develop

Conversation

@VanessaPhippsCfa
Copy link

@VanessaPhippsCfa VanessaPhippsCfa commented Mar 12, 2026

Description

I want to use Able Player in a Vue project as an NPM dependency, and this PR would make it possible to do so.

As a side effect, this PR makes Able Player better encapsulated even for IIFE users. Icons and translations are now bundled, rather than needing to be served at a particular path.

The "101 files" count is a bit misleading, I tried hard to keep this diff as small as practical while still achieving everything. 58 of those are the deleted contents of button-icons. Many of the rest are relatively minor changes from an IIFE to a module.

Would fix #189

I'll describe my changes in detail below the PR template.

How Has This Been Tested?

  • Running on a Macbook with macOS 15.7.4, using npm run watch to test the new build. I removed the build products from the PR, as instructed in CONTRIBUTING.md. Demos tested via Chrome 146.0.7680.31 .
  • Node version 22.17.0
  • npm version 11.11.0
  • grunt-cli v1.4.3
  • grunt v1.6.1
  • Verified that all the demo pages still work, when served locally by python3 -m http.server --bind 127.0.0.1 8888
  • Verified that the tests still work, at least, there is only 1 failure, the same failure currently failing in upstream develop.
  • Used a separate branch to test publication to a private NPM repository. Verified this test package works in a Vue project.

Screenshots (jpeg or gifs if applicable):

N/a, the player works the same as before.

Types of changes

This includes new features, breaking changes, and bug fixes. More details below.

Checklist:

  • My code is tested.
  • My code has proper inline documentation.

Major rewrite details follow

Open questions

  • I chose Rollup for the builder, is that ok? It seems to be the new, widely-adopted thing for bundling ES modules, and still supports UMD (which is IIFE, CommonJS, and require.js together)
  • What ES version do we want to target? There's async and await in the source now, so we're at least ES 2017. I would like to switch all those Object.prototype.hasOwnProperty.call to Object.hasOwn, but that requires ES 2022. Which has 94% availability, compared to 95% for ES 2017, and 96% for ES 2015. terser is configured to output ES 2015, we should bump that up to ES 2017 at least, and further if we want to.
    • Actually, with my new translation bundling, we don't need async anymore.
  • What is this version's number? 4.9.0? The changes are a bit dramatic for a minor version increase. Maybe this can be 5 and the jQuery removal can be 6.

New features

  • Able Player can now be installed in node_modules and bundled alongside Vue, React, Angular, etc. It now skips initialization that requires window if window is not available, enabling server-side rendering and static site generation.
  • Able Player is now built as a UMD, so can still be used as an IIFE, and probably also now works with the require.js loader. I didn't test that part, I haven't used require.js myself.
  • Able Player can be created and disposed programmatically. Use the constructor to create one, and dispose when you're done with it. This prevents memory leaks and enables global Able Player keyboard shortcuts when only one instance is active.

Improved dev experience

  • Able Player now builds TypeScript type definitions
  • npm run watch rebuilds Able Player on the fly as you're working on it

Architecture notes

  • Instead of extending Able Player on import, all the various modules expose a single function that does the extension when called. This prevents side effects on import. Which is cleaner and also allows the imports to be alphabetized in a different order than the initialization, though I'm not sure the initialization order matters.
  • Ideally the ES module wouldn't even need building, we would just put an entry point somewhere and expose that, basically what scripts/main.js currently is. However, we need to strip console.log, so that won't work yet. If we end up gating log lines behind this.debug, we could stop building ES.
    • I might be overstating this, since importing the json translations might not work depending on the application builder and its configuration.
  • Now that the package.json has "type": "module", CommonJS files in the repo had to be renamed to .cjs to run properly. The only files affected were:
    • Gruntfile.cjs
    • The Jest config and test scripts

Features I considered but didn't do

  • Optionally passing an object to the constructor for configuration. This would be alongside the existing data attribute configuration. But, didn't get to it, the data attributes are good enough, even for Vue etc.
  • Having this.debug determine whether logs are output, instead of determining this at build time. For another day.
  • js-cookie still has to be added globally, even when using Able Player as an ES module. This seems fine though, since it's an opt-in feature.

Breaking changes

  • Data attribute changes:
    • data-root-path is dropped. ES modules aren't really supposed to need this kind of thing, to fetch their own static assets, they're supposed to be bundled. So that's what I did, with translations. The image SVGs were already bundled.
    • data-icon-type is dropped. All icons are now always SVGs. SVG adoption in browsers is 96% so this should be fine.
  • window.AblePlayerInstances is no longer an array on window. It's a Set on the AblePlayer constructor. Able Player instances now add themselves to this set on construction. Applications should dispose instances when dynamically unmounting them, to prevent memory leaks and enable global Able Player keyboard shortcuts when only one instance is active.
  • ableplayer.dist.js no longer exposes DOMPurify on window. I think this is an improvement, so people can opt-in to separate DOMPurify that IS also on window, or otherwise have AblePlayer be totally encapsulated. It did break search2.html so I switched that to the separate DOMPurify build.
    • Relatedly, the demos can now use the separate-dompurify builds, since they no longer depend on the directory structure to find button icons, translations, etc.
  • Various other internal Able Player modules are no longer on window either. We could put these on AblePlayer if we thought they were important to expose, but I didn't want to do that until I was sure it was necessary.
    • window.AccessibleDialog
    • window.AccessibleSlider
    • window.validate
  • In summary, the only thing Able Player now adds to window is window.AblePlayer, and it doesn't even do that if imported as an ES module.

Bugfixes

  • Region-specific language tags are supposed to have the region capitalized. pt-BR and zh-TW. We were lowercasing them before. We might want to make this matching more lenient on casing.
  • Various issues with Vimeo error handling. The catch blocks were referencing nonexistent variables.

Code cleanup

  • ES modules are always in "strict" mode, which immediately caused breakage. I installed eslint and got all the code to pass it, to make sure I caught everything. This involved a lot of removing dead variables, double declarations, missing declarations, etc.
  • Some of these might have fixed bugs: in translation.js for example, this.lang looked like it might always be undefined, and the lang in getSampleDescriptionText looked like it'd always be undefined.
  • Unused vars and arguments are now always named e (usually "event" or "error"), for consistency.
  • something.hasOwnProperty('blah') was switched to Object.prototype.hasOwnProperty.call(something, 'blah'). More cumbersome, but more in keeping with modern practice, at least according to eslint. I think it's no longer considered safe to assume literally every object has all the Object.prototype functions available. Object.hasOwn is even more modern but requires ES 2022.

Things I didn't clean up

  • I deliberately left the indentation as similar as practical, to reduce diff noise. I figure we can fix it later.
  • I'm not sure how the require('xml-js') thing in ableplayer-base is supposed to work. Lmk if this needs more attention.
  • Most of the code is still var based. I threw in the occasional let or const when it made sense but tried to stick with vars for consistency.
  • Various demos might need their copy updated. For example, some insist that only the first player obeys keyboard shortcuts, when actually, the instance count determines whether to use the window handler, or handlers on each individual player.
  • I didn't include any documentation for use as ES modules. I figure we can do that once it's live on NPM.
  • Deprecation warning re: slider-vertical: "[Deprecation] The keyword 'slider-vertical' specified to an 'appearance' property is not standardized. It will be removed in the future. Use <input type=range style="writing-mode: vertical-lr; direction: rtl"> instead."

Existing bugs I found but didn't fix

  • Something is wrong with the sample audio for descriptions. Sometimes it stops reading anything for quite some time. Seems to be related with audio descriptions having played, or having picked too many options in the sample dialogue box.
  • I was unable to get Voice Over to read aloud descriptions done NOT by the browser voice API. Not sure what's going on there, I only just started experimenting with Voice Over.
  • playlist2.html , when clicking around the playlist rapidly, results in a large number of "Uncaught (in promise) AbortError: The play() request was interrupted because the media was removed from the document. https://goo.gl/LdLk22". I attempted to fix this with treating play() as returning a promise but I was not successful. Shrug.
  • playlist3.html: for the "IT accessibility" video, seeking has no effect. The others work fine.
  • The "next" and "previous" track buttons for playlists, seem to not work. They just restart the current media.
  • The width attribute has no effect in other-width.html. Only CSS width works.
    • For YouTube and Vimeo, things sorta work. Just the video itself seems to change shape. The controls are still max width.

Test status

  • jest still works as before. 1 test failing for reasons I'm not quite sure, but it's the same test that's already failing in upstream develop.

Files included in git but excluded from npm

  • /media
  • /demos

This is crucial to keep the NPM package size manageable: about 15 megs, instead of 150.

Also use "e" instead of "_" for ignored caught errors
Caught with eslint. At least, they look like bugs. Hopefully the buggy
behavior wasn't important.
This keeps with our allowing unused principle, and also fits better,
Vimeo does document these as "events"
Looks like various elements used to get passed around differently, but
the old references are still present, just unused or always undefined.
Get rid of 'em.
@VanessaPhippsCfa
Copy link
Author

Just noticed I was a bit overzealous when I first converted .hasOwnProperty to Object.hasOwn, then converted all those to Object.prototype.hasOwnProperty.call. There were already some Object.hasOwn in upstream develop that I accidentally also "downgraded" to Object.prototype.hasOwnProperty.call. Does that mean we're cool with requiring ES 2022? That'd be fine with me.

@joedolson
Copy link
Member

Questions:

Rollup: That seems fine to me. I've mostly used esbuild in the past, but I'm not committed to anything in particular, and don't see any good reason not to use rollup.

ES version: Yeah, those are some very fine distinctions, with a range from 94% to 96%. Switching up to 2017 is obvious, since we're already there in the code. I'm inclined to agree with ES 2022, for two reasons: 1) there isn't currently any automatic update for most users of Able Player, since it's not available through npm, so there's relatively little pressure to update and 2) my entire goal is to modernize the code base, and it just makes sense to continue in that direction.

Additionally, following up from your second comment - Object.hasOwn is already in the released code, and I've had no problems reported from that. So yes, let's just commit to moving to ES 2022 for this.

Release version: My original intention was for the next release to be 4.9, but that was before this PR. I would either choose to do a release of 4.9 without this change in May, and add this to 5.0, or skip 4.9 and release this as 5.0. I'm fine with either choice; but obviously the latter choice will be easier in many ways, as it won't require a lot of updating of this PR. A release with this change would be preferred to not include any additional feature updates, because I don't want to have to deal with testing feature changes simultaneously with ensuring we don't have any regressions.

Features:

Woo hoo!

I've never used require.js either. We should probably test that.

Will need documentation updates to cover the new capabilities.

Improved dev experience:

Also woo hoo! :)

Architecture notes:

One thing we might want to also introduce, that's been on my todo list, is reduced builds. There are some features that I wouldn't consider valuable in a standard build - e.g., the VTS. The metadata library might also be unnecessary, but it's only 140 lines, so not really a major concern.

Features not considered:

That's reasonable. I've had a few occasions where I've wanted to be able to pass an object, but I wouldn't consider it a priority. It can be added later.

Agree about this.debug. In general, I've tried to keep my scope from creeping as much as possible; it makes the project history easier to follow and keeps changes under control. Having releases every few months means that there's never a reason to rush a change in.

Also agree about js-cookie. Since it's now optional, I wouldn't bundle it.

Breaking changes:

data=root-path: I looked through instances of repos using this attribute on github, and I don't think this should cause problems in most cases. The only problem area would be cases where users were swapping the icons. This is the main reason I haven't dropped non-SVG icons yet. However, from practical terms, I don't have any data about how common this might be. I'm in favor of dropping non-SVG icons, and if we're releasing 5.0, I'm pretty much OK with making major breaking changes like that. I don't really think it's beneficial to the project to maintain the complexity of multiple types of icons in the long run.

data-icon-type: As mentioned above...

window.AblePlayerInstances no longer an array: It's a good change, and scenarios where it's relevant should be rare. As previously mentioned, this is a breaking change I'm willing to make in a major release. In searching, I found only one instance where it was in use.

Having only AblePlayer on window seems like a good choice to me. AccessibleDialog, AccessibleSlider, and validate should only be used internally; I don't see a good reason to add them to window.

Bug fixes:

👍

Code cleanup:

I've spent a lot of the last few months removing dead variables...there were a lot of them when I inherited this.

I might prefer to switch the unused vars to be more declarative; actually using event or error, depending on context, but I don't think that's necessary to update immediately.

Uncleaned up:

I've been generally avoiding making nmajor indentation changes. At some point, I'm going to want to fix all of that...but definitely not at the same time as doing other major tasks. Diff noise is just not helpful...so thank you for not doing that!

Switching variable declarations is also something I've been leaving for later, for the same reason. I don't have any particular desire to continue using var, but feel like it's a task that needs to be handled independently.

We can actually update the documentation in this branch at any time; it won't be pushed live until we're ready.

The deprecation warning is long-standing, but it still works, and the alternative still doesn't fully work. So for the time being, we're stuck with it.

Existing bugs:

If you have a chance, opening individual issues for those would be great; but I can do it, too. Just helpful if you're able to provide any additional context when opening them.

Exclusions: You should be able to exclude everything excluded in the .gitattributes file, and both /demos and /media are exluded there. Definitely don't want those exported.

@joedolson
Copy link
Member

Concerns

One thing I'd like to be able to do is allow people to import their own custom translations. The same is true for icons; I'd like to be able to pass custom icons.

In both cases, those might be good future features available by passing an Object to the constructor. Since we are losing the ability to pass custom icons via file paths and also losing the ability to use a custom translation file, those might be arguments in favor of getting the constructor arguments in place sooner rather than later.

Non-concerns that should be flagged in release notes

The file size has increased; however, with translations now packaged, there's no longer a need to asynchronously fetch those, so the overall performance impact is almost certainly an improvement. (I only say 'almost' because I haven't actually tested it.)

@VanessaPhippsCfa
Copy link
Author

VanessaPhippsCfa commented Mar 16, 2026

Thanks so much for the review!

Re: concerns

One thing I'd like to be able to do is allow people to import their own custom translations. The same is true for icons; I'd like to be able to pass custom icons.

In both cases, those might be good future features available by passing an Object to the constructor. Since we are losing the ability to pass custom icons via file paths and also losing the ability to use a custom translation file, those might be arguments in favor of getting the constructor arguments in place sooner rather than later.

Understandable. Fwiw people could still do their own custom build with extra translations. That said, especially since Able Player is currently distributed as an IIFE, I wouldn't expect most users to be familiar with bundling.

Requested changes

Lemme summarize my understanding of these, let me know if you'd change this list:

  • Document the changes: new features, breaking changes
  • Use Object.hasOwn and tell Terser that ES 2022 is ok
  • Change references to "4.9.0" to "5.0.0"

Further testing

I ended up testing RequireJS after all and it seems to work fine :D so I can document that without guilt now!

@joedolson
Copy link
Member

Yes, that's my main concern. Right now, you can just drop in a replacement file, which is within the scope of what more people can do. (For the record: I don't know that anybody is doing that, it's just relatively easy.)

But I don't think that requiring a build step to do customization is ideal. And I'd like to consider the case where somebody really only wants to customize a handful of terms, and could just pass in an object containing the replacement terms.

I don't know if this is necessary for this release, but it's something to consider.

But your list of changes is a good match.

My inclination is to merge this PR sooner rather than later, to ship an early beta release that I can send out for testing. With such extensive architectural changes, I think that's a priority. I might end up pushing the release a little later, as well - I'd been planning for May, but with changes of this scope, I think I'd want the release to fall further into the summer, to minimize possible concerns with schools.

@VanessaPhippsCfa
Copy link
Author

VanessaPhippsCfa commented Mar 18, 2026

Is there a way I can check the home page rendering of the README? I played with Jekyll a bit but couldn't figure out how the docs here are generated. Seems like it's just that one page, and the rest are inside /demos?

Edit: I think I figured it out. I needed to make a Gemfile from a fresh jekyll new, copy it into the root, replace jekyll with github-pages in it, and then I could do the bundle install then bundle exec jekyll serve. Should we write this up somewhere?

@VanessaPhippsCfa
Copy link
Author

I filled out contributing.md with some updates. Do you want to ask that new contributions pass ESLint? I think it'd be prudent.

@joedolson
Copy link
Member

Well...the docs. To be honest, they're on my list for an overhaul, which makes documenting how the current process works feel like a lower priority. I'd like to move them all into the jekyll docs build, rather than having two separate types of doc files - but that will mean changing a number of other things about how it's all setup. It'll be a lot more flexible, however.

I'm considering using the modified version of just-the-docs that we're using for the WP Accessibility documentation.

Re: new contributions guidelines. Yes, I think it would be prudent to ask for ES lint to pass in new contributions, now that the build requires strict.

Copy link
Member

@joedolson joedolson left a comment

Choose a reason for hiding this comment

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

These are pretty minor changes, but there are a few things to address, for completeness.

@@ -349,6 +351,7 @@ var AblePlayerInstances = [];
if ($(media).data('use-ttml') !== undefined) {
Copy link
Member

Choose a reason for hiding this comment

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

The TTML to VTT library is an incomplete library; I removed it from the package in version 4.6.0. This block should also be removed; I just missed it when I removed it from the build om a9d285d

@@ -1045,13 +1050,14 @@
// 2. span that contains the icon font (in our case, buttonIcon)
Copy link
Member

Choose a reason for hiding this comment

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

Looks like there are still comments to be removed; this refers to the font icon buttons, which should also be omitted.

if ( existingIcon.length > 0 ) {
return;
}
$button.find('svg, img, span').remove();
Copy link
Member

Choose a reason for hiding this comment

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

This shouldn't need to remove img or span anymore, now that we're only using svg.

@@ -1,8 +1,7 @@
(function($) {
function addTtml2webvttFunctions(AblePlayer) {
Copy link
Member

Choose a reason for hiding this comment

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

This file is currently unused. I'm inclined to remove it, as it's not a high priority. At this point, if I include it, I'm more inclined to use an existing library, rather than rolling it independently.

With all the other structural changes here, I think it's a good time to remove these old, unused files.

}
catch (err) {
catch (e) {
// Errors are suppressed here, not sure why - VP 2026-03-02
Copy link
Member

Choose a reason for hiding this comment

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

I don't know either. Predates me. May be a good idea to show them again; if this function is returning masked errors, it would be good to know.

@@ -0,0 +1,15 @@
.DS_Store
test.html
thirdparty/jwplayer.*
Copy link
Member

Choose a reason for hiding this comment

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

jwplayer was removed from Able Player in 2022, so this and any other references can go.

@terrill, @candideu, @xerc, @jbylsma, @amartincua, @zwiastunsw, @conorom, @jeanem, @joedolson, @Justryuz, @dependabot

**Full Changelog**: https://github.com/ableplayer/ableplayer/compare/v4.5.1...v4.6.0 No newline at end of file
**Full Changelog**: https://github.com/ableplayer/ableplayer/compare/v4.5.1...v5.0.0
Copy link
Member

Choose a reason for hiding this comment

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

This would be the full changelog since I took over, which I don't think is super useful. We should instead showing the the spread from e.g. v4.8.0...v5.0.0 in the changelog block for 5.0.

@joedolson
Copy link
Member

One oddity is that I haven't been able to get npm run build to run locally, though npm run watch is working just fine. I'm getting an ENOENT error running build. I haven't figured out why, yet; if you have any ideas, let me know.

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.

2 participants