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

Feat: exports for Base Plugin #3495

Merged
merged 9 commits into from
Jan 19, 2024

Conversation

brian-byassee
Copy link
Contributor

@brian-byassee brian-byassee commented Jan 16, 2024

Short description

Exporting BasePlugin and render as named exports

Resolves #
Allows consumers to create custom plugins. Currently there are no cjs exports in the dist folder for base-plugin and render which causes linting to fail

Implementation details

How to test it

import WaveSurfer from 'wavesurfer.js'
import BasePlugin from 'wavesurfer.js/dist/base-plugin.js'
import createElement from 'wavesurfer.js/dist/dom.js'

console.log({ WaveSurfer, BasePlugin, createElement })

Screenshots

Checklist

  • This PR is covered by e2e tests
  • It introduces no breaking API changes

rollup.config.js Outdated
@@ -21,7 +21,7 @@ export default [
output: {
file: 'dist/wavesurfer.cjs',
format: 'cjs',
exports: 'default',
exports: 'named',
Copy link
Owner

Choose a reason for hiding this comment

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

What implications does this have for the main WaveSurfer export? Will require('wavesurfer.js') work the same?

Copy link
Contributor

Choose a reason for hiding this comment

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

@katspaugh It looks like it is modifying the wavesurfer.cjs file
Instead of module.exports=blah it will be exports.default=blah

I think this would impact NodeJS apps in having to change their import from:
const WaveSurfer = require('wavesurfer.js')
to:
const WaveSurfer = require('wavesurfer.js').default

But I could be wrong about that.

Copy link
Owner

Choose a reason for hiding this comment

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

We should double-check that it’s not a breaking change. Especially for the min.js file which people tend to hot-load.

Copy link
Contributor Author

@brian-byassee brian-byassee Jan 17, 2024

Choose a reason for hiding this comment

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

Yeah, we checked and this is a breaking change for anyone using require to import wavesurfer

Copy link
Owner

Choose a reason for hiding this comment

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

I would suggest not to add those exports to the main module then.
Why can’t one just import base plugin directly from dist?

https://unpkg.com/[email protected]/dist/base-plugin.js

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Eslint could not resolve the exports. The application would run but eslint would crash.

We updated the PR to export base-plugin and dom. Please take a look at the exports section in the package.json.

When there is a major version bump we should look at updating how exports are handled to make it easier to add new exports in the future. :)

src/wavesurfer.ts Outdated Show resolved Hide resolved
@brian-byassee brian-byassee marked this pull request as draft January 17, 2024 16:41
@brian-byassee brian-byassee marked this pull request as ready for review January 17, 2024 17:41
@brian-byassee brian-byassee marked this pull request as draft January 17, 2024 17:50
package.json Outdated
@@ -42,6 +42,16 @@
"types": "./dist/plugins/*.d.ts",
"require": "./dist/plugins/*.cjs"
},
"./dist/base-plugin.js": {
"import": "./dist/base-plugin.esm.js",
Copy link
Owner

Choose a reason for hiding this comment

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

This might backfire because the main bundle will have the same imports but a different copy thereof. So you would basically ship duplicate code, plus the class/type identity will differ. Wavesurfer expects plugins to be subclassesof that specific BasePlugin import, not another copy. Hope this makes sense, it barely does to me :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup I think this PR still has some issues beyond just that. Demoted it to a draft again and will look at it some more here in a bit. But I think I might just end up closing it for now.

More to come soon!

Copy link
Owner

Choose a reason for hiding this comment

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

Looks like it's possible to ignore named exports for CommonJS modules via a rollup plugin.

Another alternative, just to acommodate the CommonJS module, would be to expose BasePlugin as a static property of the WaveSurfer class instead of exporting it as a named export.

Copy link
Contributor

Choose a reason for hiding this comment

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

This might backfire because the main bundle will have the same imports but a different copy thereof. So you would basically ship duplicate code, plus the class/type identity will differ. Wavesurfer expects plugins to be subclassesof that specific BasePlugin import, not another copy. Hope this makes sense, it barely does to me :)

Just to clarify, this is already an existing problem. The current exports do not use the same BasePlugin class instance. Let me give an example.

import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.esm.js';
import TimelinePlugin from 'wavesurfer.js/dist/plugins/timeline.esm.js';

Both of these imports are distinct compilations of the source code with their own cjs/esm/min bundles. They contain the code for BasePlugin individually and are not referencing the same instance of the class.

This can be illustrated by running the following code:

console.log('same proto?', RegionsPlugin.__proto__ === TImelinePlugin.__proto__);

Or using instanceof:

const regions = new RegionsPlugin();
const timeline = new TimelinePlugin();
console.log('instanceof RegionsPlugin.__proto__?', regions instanceof RegionsPlugin.__proto__);
console.log('instanceof TimelinePlugin.__proto__?', regions instanceof TimelinePlugin.__proto__);

The way to fix this would be to modify the library to return a single bundle.

import WaveSurfer, { BasePlugin, TimelinePlugin, RegionsPlugin } from 'wavesurfer.js'

// Now TimelinePlugin.__proto__ === RegionsPlugin.__proto__ === BasePlugin

You can get this to work pretty easily by having a root index.js that contains the exports. Use rollup to build one single bundle and export it all. It lends itself to tree shaking this way, too.

But it would be a breaking change to people so this would require a major version bump.

Copy link
Owner

Choose a reason for hiding this comment

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

True, and thanks for Index.ts pointer.

@brian-byassee brian-byassee marked this pull request as ready for review January 17, 2024 20:49
@@ -140,6 +141,9 @@ class WaveSurfer extends Player<WaveSurferEvents> {
protected subscriptions: Array<() => void> = []
protected mediaSubscriptions: Array<() => void> = []

public static readonly BasePlugin = BasePlugin
public static readonly createElement = createElement
Copy link
Owner

@katspaugh katspaugh Jan 18, 2024

Choose a reason for hiding this comment

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

Sorry for the back and forth on this, but now that it's a static method of WaveSurfer, it would be good to not name it similar to the existing create method to avoid confusion when using autocomplete.

I suggest we do

import * as dom from './dom.js'

// ...

  public static readonly dom = dom

createElement could then be a named export.

And plugin authors could then use it like this:

const { createElement } = WaveSurfer.dom

WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No worries at all dude! I updated the code.

* only imports named exports from a file. So I needed to update dom.js to have named export createElement.

I think we'd need to leave the default export since others maybe importing the default from dom.js in their projects.

Copy link
Owner

@katspaugh katspaugh left a comment

Choose a reason for hiding this comment

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

Thank you, LGTM 👍

@katspaugh katspaugh merged commit e5b32d6 into katspaugh:main Jan 19, 2024
2 checks passed
@brian-byassee brian-byassee deleted the feat-base-plugin-export branch January 23, 2024 17:03
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.

4 participants