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

Switch from Reify to SWC for ensuring compatability with older browsers. #693

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

Conversation

ILOVEPIE
Copy link
Contributor

@ILOVEPIE ILOVEPIE commented Mar 31, 2024

Description

Created a utility that runs esbuild and SWC to build the library.
This supports a compat profile that works as far back as safari 8, maybe earlier.

This closes #619

Motivation and Context

The library is limited in compatibility by the number of new features we are using, this PR allows us to keep using those features while dropping reify and also supporting even older browsers.

How Has This Been Tested?

I tested the compat outputs using Browser Stack on multiple old browsers including safari 8 and some ancient versions of chrome. I also tested the other builds using more modern browsers.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

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.
  • I have read the CONTRIBUTING document.

@ILOVEPIE ILOVEPIE added enhancement dependencies Pull requests that update a dependency file dev Changes revolving merely around dev-related code like testing, build process, etc. labels Mar 31, 2024
@ILOVEPIE ILOVEPIE added this to the Release 2.0.0 milestone Mar 31, 2024
@ILOVEPIE ILOVEPIE requested review from yne and Connum March 31, 2024 22:28
@ILOVEPIE ILOVEPIE force-pushed the useSWCForVersionTranslation branch from b7f0fcb to c23e16e Compare March 31, 2024 23:16
@ILOVEPIE
Copy link
Contributor Author

ILOVEPIE commented Apr 1, 2024

@yne does this look OK or do we need to make changes?

@yne
Copy link
Contributor

yne commented Apr 2, 2024

to be honest

  • I think it's a step in the wrong direction because I expect the opentype v2 to move to a modern stack with less complexity
  • If you want to use older browser, use older opentype.js
  • WebKit/apple are well known for being anti-web so I doubt they users (who exactly ?) expect anything
  • This commit add even more build script to maintain which I'm fighting against ( eslint-local-rules.js I'm also looking at you)
  • esbuild support transpiling to older ES version https://esbuild.github.io/api/#supported and I don't see any explanation why it is not used
  • This commit remove mocha tests ?!

So no, I won't accept it.

@ILOVEPIE
Copy link
Contributor Author

ILOVEPIE commented Apr 2, 2024

to be honest

  • I think it's a step in the wrong direction because I expect the opentype v2 to move to a modern stack with less complexity
  • If you want to use older browser, use older opentype.js
  • WebKit/apple are well known for being anti-web so I doubt they users (who exactly ?) expect anything
  • This commit add even more build script to maintain which I'm fighting against ( eslint-local-rules.js I'm also looking at you)
  • esbuild support transpiling to older ES version https://esbuild.github.io/api/#supported and I don't see any explanation why it is not used
  • This commit remove mocha tests ?!

So no, I won't accept it.

The problem is older versions of OpenType.js don't work properly. Also, we already discussed all this in #619.

Also, esbuild does not support transpiling. It only errors when you use a feature that isn't supported by the target. I did a lot of research into this. Also, old versions of Safari are not the only thing targeted by the compat profile. It also supports old versions of Firefox and Chrome and old browsers like Internet Explorer. While you may think there is no use in supporting older browsers, I think there is a large amount of developers out there who work with legacy code who would appreciate being able to use a modern library. Also, do you know how many modern smart TVs are still running old versions of WebKit? Nearly all of them. So if somebody wanted to use the library in a smart TV app, then they're SOL without this. I'm not suggesting we make this the default version of opentype. I'm just saying it's okay to support older browsers for those who need it. And besides, this allows us to drop reify for a dependency that's actually up to date: SWC.

Hell, I'll even take over complete responsibility for maintaining the compat profile if you're that against including the profile.

It's not like I'm asking for Netscape Navigator support.

@Jolg42 & @Connum should weigh in with their opinions before a decision is made.

@ILOVEPIE
Copy link
Contributor Author

ILOVEPIE commented Apr 2, 2024

  • This commit remove mocha tests ?!

Yeah, that was a mistake. I just saw the word "reify" and removed it. I was pretty tired at the time.

@yne
Copy link
Contributor

yne commented Apr 2, 2024

Also, esbuild does not support transpiling. It only errors when you use a feature that isn't supported by the target. I did a lot of research into this.

I'm confused, the esbuild documentation state that

[--target] tells esbuild to transform JavaScript syntax that is too new for these environments into older JavaScript syntax that will work in these environments. For example, the ?? operator was introduced in Chrome 80 so esbuild will convert it into an equivalent (but more verbose) conditional expression when targeting Chrome 79 or earlier.

So I would have expected some kind of rational/justification explaining why esbuild was not the chosen solution.

I'm not against allowing users to build a "compat" version of opentype.js. I'm against complexifing our codebase/buildflow for that.
if it would only have required a --target=es5 to the esbuild I wouldn't have minded.

@yne
Copy link
Contributor

yne commented Apr 2, 2024

also, the increasing number of variability that we have to test may require some re-focus

  • esm/cjs
  • low-memory
  • min
  • legacy

sum up to 16 possible build profile combination to test

maybe low memory and legacy are for the same purpose ?

I hope that even if we disagree on this subject you'll still ping me some other times 😀

@ILOVEPIE
Copy link
Contributor Author

ILOVEPIE commented Apr 2, 2024

Also, esbuild does not support transpiling. It only errors when you use a feature that isn't supported by the target. I did a lot of research into this.

I'm confused, the esbuild documentation state that

[--target] tells esbuild to transform JavaScript syntax that is too new for these environments into older JavaScript syntax that will work in these environments. For example, the ?? operator was introduced in Chrome 80 so esbuild will convert it into an equivalent (but more verbose) conditional expression when targeting Chrome 79 or earlier.

So I would have expected some kind of rational/justification explaining why esbuild was not the chosen solution.

I'm not against allowing users to build a "compat" version of opentype.js. I'm against complexifing our codebase/buildflow for that.
if it would only have required a --target=es5 to the esbuild I wouldn't have minded.

esbuild does transform some syntax but it's a very limited transform and it's not as thorough as SWC. Also SWC does a code optimization pass to make the code smaller and more performant by rearranging elements of the code.

Also:

Note that this is only concerned with syntax features, not APIs. It does not automatically add polyfills for new APIs that are not used by these environments. You will have to explicitly import polyfills for the APIs you need (e.g. by importing core-js). Automatic polyfill injection is outside of esbuild's scope.

SWC does automatic ponyfill injection. So for example we have usages of map that are not supported in older browsers because map was introduced later on. SWC fixes this automatically by injecting a ponyfill of Array.prototype.map that gets used if the function is not available in the browser. It also only injects the ponyfills needed to target a specific browser version at a minimum. So what it does is it only injects the minimum number of ponyfills needed for that target.

@ILOVEPIE
Copy link
Contributor Author

ILOVEPIE commented Apr 2, 2024

The reason I was talking about the erroring was because you originally only linked the supported flag, which is not the same as the target flag.

@Connum
Copy link
Contributor

Connum commented Apr 2, 2024

I actually like this PR, and as @ILOVEPIE stated, we already had a discussion about it. Getting rid of reify will finally allow us to use modern syntax and language features, such as private class fields and methods. iirc it was also holding us back from switching upgrading to the most recent version of mocha. So it actually is a step to more modern development. SWC is also a modern build tool, and many active projects provide legacy versions (may using a "hail Mary" approach and not even running the tests on them again) so I frankly don't get that argument at all. 😄

I don't really care about increased build times, and the complexity is no really increasing that much. There are still so many projects relying on webpack, babel and a great number of plugins for them... So what we'd have now is already way better than that.

Regarding the local eslint rules you mentioned: these are for specific performance improvements that we found made an impact on the library, and there were no existing packages for them. You write them once, nothing to maintain except extending them maybe, they're only used for lining, so I don't really see an issue with that either. I think it's even better to have control over them instead of having to rely on yet another third party package.

So I'm very happy to see this happening and I'm all for it!

let esbuildTarget;
{
//These are the targets for a compatibility build that should work with all browsers in use today, even in embedded devices.
const compatTargets = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we could put these into package.json and retrieve them from there, so it will be easier to maintain, as we'll probably forget about this file if we don't have to touch it very often.

@Connum
Copy link
Contributor

Connum commented Apr 2, 2024

Furthermore, I strongly believe that built complexity or maintenance never was and never will be the reason why opentype.js development is slow. It's our work and private lifes and the lack of sponsorship. 😉

@yne
Copy link
Contributor

yne commented Apr 2, 2024

If this allows removing the barrier on mocha then I'll be for the best. ✔️

Is the Closure Compiler externs file still relevant once transpiling will work ?

Echoing to #675 and the last line of #693 (comment) I think one day we shall have a clear view of what opentype.js vision is: "sleek & modern" or "battery-included works-everywhere " (kind of a mac vs windows)

package.json Outdated
"d:umd": "esbuild --bundle src/opentype.js --outdir=dist --external:fs --external:http --external:https --target=es2018 --format=iife --out-extension:.js=.min.js --global-name=opentype --footer:js=\"(function (root, factory) { if (typeof define === 'function' && define.amd)define(factory); else if (typeof module === 'object' && module.exports)module.exports = factory(); else root.opentype = factory(); }(typeof self !== 'undefined' ? self : this, () => ({...opentype,'default':opentype})));\" --minify --sourcemap",
"b:esm": "esbuild --bundle src/opentype.js --outdir=dist --external:fs --external:http --external:https --target=es2018 --format=esm --out-extension:.js=.module.js",
"d:esm": "esbuild --bundle src/opentype.js --outdir=dist --external:fs --external:http --external:https --target=es2018 --format=esm --out-extension:.js=.module.min.js --minify --sourcemap"
"start": "node esbuild-runner.mjs --input src/opentype.js --output dist/opentype.module.js --externals \"['fs','http','https']\" --target es2015 --module --watch --servedir . --global-name opentype --footer \"(function (root, factory) { if (typeof define === 'function' && define.amd)define(factory); else if (typeof module === 'object' && module.exports)module.exports = factory(); else root.opentype = factory(); })(typeof self !== 'undefined' ? self : this, function(){return ((function(a,b){var keys=Object.keys(b);for(var i=0;i<keys.length;i++)a[keys[i]]=b[keys[i]];return a})({'default':opentype},opentype));});\"",
Copy link
Contributor

Choose a reason for hiding this comment

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

why are fs/http/https back in town ?

@ILOVEPIE
Copy link
Contributor Author

ILOVEPIE commented Apr 2, 2024

If this allows removing the barrier on mocha then I'll be for the best. ✔️

Is the Closure Compiler externs file still relevant once transpiling will work ?

Echoing to #675 and the last line of #693 (comment) I think one day we shall have a clear view of what opentype.js vision is: "sleek & modern" or "battery-included works-everywhere " (kind of a mac vs windows)

the two approaches aren't mutually exclusive. That closure compiler externs file is outdated and needs fixes, it basically tells google closure compiler what our API is so it doesn't get mangled or optimized away when people using closure compiler use opentype.js. Closure compiler is like SWC's less hip but way stronger older brother. I opted for SWC because it's newer but that doesn't mean our users will be using it over closure compiler.

@yne
Copy link
Contributor

yne commented Apr 3, 2024

  • This commit remove mocha tests ?!

Yeah, that was a mistake. I just saw the word "reify" and removed it. I was pretty tired at the time.

@ILOVEPIE : I've cloned your branch and re-enabled unit-tests (so without --reify) and it failed.

Did you created the PR without running the unit-test ? Or where you using a solution that you didn't commited ?

If the later (which I really hope), could you share it because the only solution we had for now was to simply declare module:true in package.json but you refused it as too modern (because it require NodeJS v15.3 from 2020?)

Also, I setup SWC from scratch by

  • adding "@swc/cli": "^0.3.12" in "devDependencies"
  • adding "compat": "swc -C jsc.target=es3 ./dist/opentype.js -o ./dist/opentype.compat.js", in script

and it worked.

So what was the motivation for creating all those swc wrapper and why was this wrapper even used on non compat builds ?

@ILOVEPIE
Copy link
Contributor Author

ILOVEPIE commented Apr 3, 2024

There was a few issues with the PR. I'm going to fix them later today. I was pretty tired when I finalized the PR and didn't fully take everything into account. The reason why I have the wrapper is because it gives us finer control over the targeting of the builds and also allows us to specifically enable or disable features of SWC that are causing issues in a specific build etc. The solution for fixing the tests I'm probably going to go with is renaming the source files to .mjs because that should let Mocha run them properly. Unless you want me to make a wrapper that runs mocha (just kidding). Also SWC assumes that it's output will be inside a IIFE or module, which means it's better to apply it before bundling.

@ILOVEPIE
Copy link
Contributor Author

ILOVEPIE commented Apr 3, 2024

Also the reason why it was used on non-compat builds was because that way the code would be ES6 compatible even if new features are added and we use them. For example, the spread operator is not ES6 compatible, but we can use it with SWC and still be ES6 compatible.

@ILOVEPIE ILOVEPIE force-pushed the useSWCForVersionTranslation branch 2 times, most recently from 9972973 to 6fefe3b Compare April 5, 2024 00:26
@ILOVEPIE
Copy link
Contributor Author

ILOVEPIE commented Apr 5, 2024

I fixed some of the test stuff to work better with modules, but I'm still getting a timeout with the HTTP and HTTPS modules.

@ILOVEPIE ILOVEPIE force-pushed the useSWCForVersionTranslation branch from 6fefe3b to 95915b4 Compare April 5, 2024 20:07
@ILOVEPIE
Copy link
Contributor Author

ILOVEPIE commented Apr 5, 2024

This also closes #692 as it required reworking the same stuff.

@ILOVEPIE
Copy link
Contributor Author

ILOVEPIE commented Apr 5, 2024

I fixed the issues, @yne if you want to take a look, this PR is now using latest mocha.

@ILOVEPIE ILOVEPIE force-pushed the useSWCForVersionTranslation branch from 95915b4 to 2de408e Compare April 5, 2024 20:13
package.json Outdated
@@ -61,7 +68,7 @@
],
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2018,
"ecmaVersion": 2023,
Copy link
Contributor

Choose a reason for hiding this comment

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

at this point we may as well use "latest" or just nothing (default=latest)
so we won't have to update it again

@yne
Copy link
Contributor

yne commented Apr 6, 2024

glad we finaly moved to module format, but I think that because of all those renaming, all other PR will now be broken ?

loadFromFile/loadFromUrl are getting more an more uggly everyday...
Can't we just remove them someday ? (for example: today)

I would really prefere using the official @swc/cli wrapper that having to read/understand thoses 500+ line of build scripts (buildLib/commandLineHelper.mjs buildLib/esbuild-plugin-swc.mjs esbuild-runner.mjs) which I did not btw.

@Connum
Copy link
Contributor

Connum commented Apr 6, 2024

but I think that because of all those renaming, all other PR will now be broken ?

I think (or maybe it's more kind of a hope) that git will be clever enough so that they can be easily rebased, however, any added files in a PR and their imports will have to be renamed

@yne
Copy link
Contributor

yne commented Apr 6, 2024

but I think that because of all those renaming, all other PR will now be broken ?

I think (or maybe it's more kind of a hope) that git will be clever enough so that they can be easily rebased, however, any added files in a PR and their imports will have to be renamed

Yes let's hope 🤞

Since #619 did not mention tsc, I'll exploring it use as JS transpiler (default to es5, which shall work on @ILOVEPIE smart toaster 😏)

@yne
Copy link
Contributor

yne commented Apr 6, 2024

Just realized by looking at #695 that all HTML page importing opentype.module.js shall also be updated

@ILOVEPIE
Copy link
Contributor Author

ILOVEPIE commented Apr 6, 2024

glad we finaly moved to module format, but I think that because of all those renaming, all other PR will now be broken ?

loadFromFile/loadFromUrl are getting more an more uggly everyday...
Can't we just remove them someday ? (for example: today)

I wouldn't remove them in 2.0.0. I would remove them in 2.0.1 or 2.1.0. Also, we didn't move to the module format. All I did was rename the existing module file names to .mjs. The thing is though I kept the .module.js option for people who are still depending on that.

@ILOVEPIE
Copy link
Contributor Author

ILOVEPIE commented Apr 6, 2024

Just realized by looking at #695 that all HTML page importing opentype.module.js shall also be updated

This PR doesn't require that, as opentype.module.js still exists. It's just deprecated now.

@ILOVEPIE
Copy link
Contributor Author

ILOVEPIE commented Apr 6, 2024

but I think that because of all those renaming, all other PR will now be broken ?

I think (or maybe it's more kind of a hope) that git will be clever enough so that they can be easily rebased, however, any added files in a PR and their imports will have to be renamed

Yes let's hope 🤞

Since #619 did not mention tsc, I'll exploring it use as JS transpiler (default to es5, which shall work on @ILOVEPIE smart toaster 😏)

The problem with using TSC is that it doesn't work with JavaScript files. We'd have to translate everything to TypeScript. Also, another thing is that the output, I think, only goes as far back as ES5.

@ILOVEPIE
Copy link
Contributor Author

ILOVEPIE commented Apr 6, 2024

but I think that because of all those renaming, all other PR will now be broken ?

I think (or maybe it's more kind of a hope) that git will be clever enough so that they can be easily rebased, however, any added files in a PR and their imports will have to be renamed

That's specifically why I used git mv instead of the regular move command.

@yne
Copy link
Contributor

yne commented Apr 7, 2024

The problem with using TSC is that it doesn't work with JavaScript files.

it does. i've used it. But you are correct on ES5 part. It wont transpile to older standards.
but it seems the new "compat" opentype targets are also ES5 so...

https://github.com/opentypejs/opentype.js/pull/693/files#diff-84f0a237b3664b42983eec0d5925a9529eb52beee9aab70776b7c21cfdb0cd65R169

@yne
Copy link
Contributor

yne commented Apr 7, 2024

I wouldn't remove them in 2.0.0. I would remove them in 2.0.1 or 2.1.0.

I would replace them with a deprecation/migration notice/console.error for 2.0.0 , then remove them in 2.1.0

that's what majors are for,
you dont break API in minor/micro

@ILOVEPIE
Copy link
Contributor Author

ILOVEPIE commented Apr 7, 2024

I wouldn't remove them in 2.0.0. I would remove them in 2.0.1 or 2.1.0.

I would replace them with a deprecation/migration notice/console.error for 2.0.0 , then remove them in 2.1.0

that's what majors are for,
you dont break API in minor/micro

Sorry, I think there was a miscommunication. That's exactly what I was saying.

But I wouldn't replace them with an error. I would just simply spit out an error to the console if they tried to use it, but still have it work.

@ILOVEPIE
Copy link
Contributor Author

ILOVEPIE commented Apr 7, 2024

The problem with using TSC is that it doesn't work with JavaScript files.

it does. i've used it. But you are correct on ES5 part. It wont transpile to older standards.
but it seems the new "compat" opentype targets are also ES5 so...

https://github.com/opentypejs/opentype.js/pull/693/files#diff-84f0a237b3664b42983eec0d5925a9529eb52beee9aab70776b7c21cfdb0cd65R169

No, there are some compatibility targets listed there that are not ES5 but rather have partial support for ES5.

@Connum
Copy link
Contributor

Connum commented Apr 10, 2024

I'd suggest we wait till the SVG support PR is merged, then bring this PR up-to-date and take care of the file renaming of files that have meanwhile been added via several PRs. Then we should get this merged quite soon, so we don't have to do that over and over again. Your thoughts, @yne @ILOVEPIE?

@ILOVEPIE
Copy link
Contributor Author

I'd suggest we wait till the SVG support PR is merged, then bring this PR up-to-date and take care of the file renaming of files that have meanwhile been added via several PRs. Then we should get this merged quite soon, so we don't have to do that over and over again. Your thoughts, @yne @ILOVEPIE?

Sounds like a plan to me.

@yne
Copy link
Contributor

yne commented Apr 11, 2024

sure, the js->mjs renames may even be done in it specific PR that could be merged instantaneously,
so we don't mixup rename / reify-drop / browser-support in the same PR

@ILOVEPIE ILOVEPIE force-pushed the useSWCForVersionTranslation branch from 2de408e to 84931e4 Compare April 15, 2024 22:42
@yne yne mentioned this pull request Apr 21, 2024
8 tasks
@yne
Copy link
Contributor

yne commented Apr 21, 2024

I succeed (more or less) to make it run on Firefox 4 (2011) using the following build script

"scripts": {
  "comp": "tsc --allowJs dist/opentype.js --outFile dist/opentype.compat.js"
}

However It lack the following classes Proxy, WeakMap, DataView, Array.includes, Array.from so I had to polyfill/mock them but in the end it works (at least it extract the name table):

image

<script src="dist/opentype.compat.js"></script>
<script>
    var xhr = new XMLHttpRequest(); // member when no fetch() ? I member.
    xhr.open('GET', 'test/fonts/TestHVAROne.otf', true); // python -m http.server it
    xhr.responseType = 'arraybuffer';
    xhr.onload = function(e) {TEST=opentype.parse(this.mozResponseArrayBuffer)};
    xhr.send();
</script>

see 9fc86d4

@ILOVEPIE
Copy link
Contributor Author

I succeed (more or less) to make it run on Firefox 4 (2011) using the following build script

"scripts": {
  "comp": "tsc --allowJs dist/opentype.js --outFile dist/opentype.compat.js"
}

However It lack the following classes Proxy, WeakMap, DataView, Array.includes, Array.from so I had to polyfill/mock them but in the end it works (at least it extract the name table):

image

<script src="dist/opentype.compat.js"></script>
<script>
    var xhr = new XMLHttpRequest(); // member when no fetch() ? I member.
    xhr.open('GET', 'test/fonts/TestHVAROne.otf', true); // python -m http.server it
    xhr.responseType = 'arraybuffer';
    xhr.onload = function(e) {TEST=opentype.parse(this.mozResponseArrayBuffer)};
    xhr.send();
</script>

see 9fc86d4

  • tsc is not as good at optimization as swc is
  • tsc is not designed for this like swc is
  • tsc doesn't do ponyfill injection like swc
  • tsc is objectively slower at this than swc

@Connum
Copy link
Contributor

Connum commented May 24, 2024

@ILOVEPIE Now that #703 has been merged, do you want to rework this PR or abandon it alltogether?

@ILOVEPIE
Copy link
Contributor Author

I'll rework it.

This supports a compat profile that works as far back as safari 8, maybe earlier.
@ILOVEPIE ILOVEPIE force-pushed the useSWCForVersionTranslation branch from 3946e83 to 1dfe804 Compare May 28, 2024 19:25
@ILOVEPIE ILOVEPIE force-pushed the useSWCForVersionTranslation branch from 1dfe804 to bfc8dd4 Compare May 28, 2024 19:27
@ILOVEPIE
Copy link
Contributor Author

@Connum @yne This PR has been reworked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dependencies Pull requests that update a dependency file dev Changes revolving merely around dev-related code like testing, build process, etc. enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Should we create a compatibility focused build in addition to the non-esm build?
3 participants