Skip to content

Migrate package to ES6 Module #579

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

Closed
wants to merge 3 commits into from
Closed

Migrate package to ES6 Module #579

wants to merge 3 commits into from

Conversation

yne
Copy link
Contributor

@yne yne commented Mar 1, 2023

Motivation and Context

Opentype.js codebase is structured as modules (see: import/export lines), but it is not declared as such (type:module in package.json) because we rely on commonJs require() syntax to import node-specific function (ex: fs). This "hybrid" usage forced us to use reify in out tests order to make mocha<=8 work (but mocha 8 also has at least 2 known vulnerabilities)

Sadly this combo don't work anymore with latest mocha v10, so we are stuck on a vulnerable version.

So here are 4 possibles structure we could use:

package type mocha v10 extensions require() Notes
commonjs 🔴 .js 🔴 current state of opentype.js
commonjs 🔴 js=>.mjs 🟢
module 🟢 .js 🔴 can't require(*.js) : .js are now considered ESM
module 🟢 .cjs+.js 🟢 👉 solution choosen 👈

I used the less breaking options (n°4) :

  • browser can still <script src="//cdn.example/opentypejs/dist/opentype.js"></script>
  • browser can still import * as opentype from "//cdn.example/opentypejs/dist/opentype.module.js"
  • old node can still require('opentype.js') which load the .cjs version (disambiguation required)
  • new node can still import * as opentype from 'opentype.js' which load the .module.js version

But declaring as type:module mean that require() must be migrated to import / import().

How Has This Been Tested?

by releasing my 2.0.1 fork on NPM and using it

with a node script main.js

opentype = require('opentype.yne.js');
// import not possible here: "Cannot use import statement outside a module"

with a node script main.mjs

import * as opentype from 'opentype.yne.js';
// require() not possible here: "require is not defined in ES module scope"

with an index.html testing both cjs/jsm cases

<script src="opentype.yne.js/dist/opentype.js"></script>
<script>console.log(opentype);</script>

<script type=module>
import * as ot from "./opentype.yne.js/dist/opentype.module.js";
console.log(ot);
</script>

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change: removal of .load() / .download() functions

Checklist:

  • I did npm run test and all tests passed green (including code styling checks).
  • I have added tests to cover my changes.
  • My change requires a change to the documentation.
  • I have updated the README accordingly.

@yne yne marked this pull request as ready for review March 1, 2023 22:58
@Connum Connum added the dev Changes revolving merely around dev-related code like testing, build process, etc. label Mar 2, 2023
@yne yne added this to the Release 2.0.0 milestone Mar 4, 2023
@yne yne changed the title [2.x.x] Migrate package to ES6 Module Migrate package to ES6 Module Mar 4, 2023
@yne
Copy link
Contributor Author

yne commented Mar 4, 2023

@ILOVEPIE / @Connum

No urgency (since 2.0.0 is not so near), but I would like to get your feeling about it.

@Connum
Copy link
Contributor

Connum commented Mar 5, 2023

I'm not a fan of dropping those functions. If we really have to, let's at least go for your suggestion:

A less breaking change would be to print a warning in the .load() .download() function to direct the user to the new usage

What about a different approach: Use dynamic imports and declare fs as an external:
https://github.com/Connum/opentype.js/tree/esm-dynamic-imports

@yne
Copy link
Contributor Author

yne commented Mar 6, 2023

Use dynamic imports

Seems like the best solution so far, but unlike synchronous require(), the import() is asynchronous. This create funny prototype like this

may as well drop the loadSync API for 2.0.0 and only keep .load() / .download() ?

@Connum
Copy link
Contributor

Connum commented Mar 6, 2023

Yeah, I found that funny too. :) but the function still works kind of synchronously itself using await, it just had to be declared as async to be able to use the await keyword.

@Connum
Copy link
Contributor

Connum commented Mar 6, 2023

I'll also take a look at

import { createRequire } from 'module';

Tomorrow, not sure about the node version requirements and browser compatibility yet.

@Connum Connum mentioned this pull request Mar 10, 2023
@yne
Copy link
Contributor Author

yne commented Mar 11, 2023

it just had to be declared as async to be able to use the await keyword.

That's what async function are for. Having an async loadSync() declaration is laughable.

I tested import { createRequire } from 'module'; and it does not work in browser

If we HAVE to keep those user-side function, can we at least drop the .loadSync() and only keep the promise part of .load() ?

But I'd like to remind that all those functions are a mess to maintain, they rely on vendors API and I was hoping to drop them all for 2.x

yne added 2 commits March 12, 2023 12:50
- use last mocha: 0 vulnerability
- remove reify deps: unsupported in last mocha version
@yne yne force-pushed the esm branch 5 times, most recently from 26a7385 to f74be7f Compare March 12, 2023 12:50
@yne
Copy link
Contributor Author

yne commented Mar 12, 2023

@Connum Here is what I did:

  • Used your import() strategy => work well 👍, I added an engines field to the new package.json in order to reflect the ES2020 minimum requirement.
  • Updated PR description to reflect that .load()/.download() are kept
  • Removed the loadSync method since it is not sync anyway (see .load() for the await version)
  • add a @deprecated to the .load() and .download() methods so we can show a replacement code.
  • use createRequire for importing CJS into ESM test
  • move the "no-foreach" rules into no-restricted-syntax declaration since
    eslint-plugin-local-rules (which shall have been declared among devDependencies) don't work with ESM
  • Remove the *.js import file extension check because ESM already enforce that.

Tell me if you have any improvement ideas

@yne yne requested a review from Connum March 12, 2023 12:50
@luxaritas
Copy link

Heads up: Attempting to use this library within my ESM app run through vite-node, and it encounters a runtime error due to the lack of "type": "module" in the package.json. For now I'm using patch-package to manually add it

@ILOVEPIE
Copy link
Contributor

Heads up: Attempting to use this library within my ESM app run through vite-node, and it encounters a runtime error due to the lack of "type": "module" in the package.json. For now I'm using patch-package to manually add it

Heads up: switching to ESM entirely will completely break support for a ton of libraries already built on opentype.js, including mine.

@yne
Copy link
Contributor Author

yne commented Jul 25, 2023

@ILOVEPIE are you sure about that breakage ? because in this PR, I took the effort to make it backward compatible. so all previous path are still there and there is now a new opentype.module.js artefact IIRC

@luxaritas
Copy link

Definitely not suggesting to remove CJS support, just to get ESM working in conjunction!

@yne
Copy link
Contributor Author

yne commented Nov 5, 2023

Shall I rebase/resolve this PR or can it still wait ?

@Connum
Copy link
Contributor

Connum commented Nov 5, 2023

I think we should wait until some of the larger PRs are merged. It's easier to then make the required code changes here than getting all the different PR authors do so.

@yne
Copy link
Contributor Author

yne commented Nov 5, 2023

Understood, I'll be ready :)

@ILOVEPIE
Copy link
Contributor

This has been resolved.

@ILOVEPIE ILOVEPIE closed this Jun 11, 2024
@yne yne deleted the esm branch June 11, 2024 20:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dev Changes revolving merely around dev-related code like testing, build process, etc.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants