fabricjs / fabric.js

Javascript Canvas Library, SVG-to-Canvas (& canvas-to-SVG) Parser
http://fabricjs.com
Other
29.2k stars 3.52k forks source link

IMPORTANT: Migrating to modern javascript/typescript #7596

Closed asturur closed 1 year ago

asturur commented 2 years ago

Hello everyone!

FabricJS is nearly 12 years old.

The library has still its place around the internet, is good for many things, is never perfect, but i know that for who approach rich canvas based interactive apps, this is a nice piece of software to leverage.

While our main fault are still lack of great docs and updated and clarifying examples, now also the javascript code looks like a lot obsolete.

There are things we could leverage like Promises, default value for arguments, fat arrow for loops and readibility, native classes, proper getters and setters.

We would like to take the occasion to revisit the shape of the codebase to something more modern. This would include proably a bundler, proper import statements instead of a single file that gets built like lego blocks, maybe tree shaking for who import just what he wants to import.

This would make the custom built unnecessary, using other libraries easier including touch interactions.

So a move to es201X is coming anyway.

The question is if we want to add type descriptors or if we want to make a TS migration right away.

The benefits of consuming TS are clear to me, the benefit of writing typescript a little less, i'm not a great fan and also i m not great a TS. Is very easy to use TS in the wrong way and produce subpar results.

We are open to suggestions and understand what is the best way to make everyone happy. external type definition in form of d.ts files are a good middle way if there is a way to enforce correctness at build time. A new lint profile is required. A new build tool too. Legacy JS support is not going away. A bundler will produce the correct code for older browsers. But probably IE11 is out of the game anyway. I would love to have 2 builds, a legacy one and a modern one ( the classic last 2 versions of each browser )

So which are your reason to list TS on the pro/cons of this change? I understand you may not like TS. that is fine. I don't like it either. But if is terse, with types out of the way, and a transpiled build is available, what is your main point against or for it?

jimmywarting commented 2 years ago

please no typescript

melchiar commented 2 years ago

@jimmywarting Typescript support is one of our most commonly requested features. Feel free to elaborate if you have specific objections to adding support for it.

jimmywarting commented 2 years ago

esm is also a desirable request, working with typescript and esm is a total nightmare. beside you can have a type safety system with vanila js + jsdoc and writing the code in such a manner that it don't require annotation... like using # instead of private, use classes instead of prototype, avoid using promise constructor and instead use async/await so the IDE can really figure out what something return and so on.

it's also possible to generate d.ts files from js also

i created https://jimmywarting.github.io/you-might-not-need-typescript/ to this end...

andrewzolotukhin commented 2 years ago

I think it's not necessary to migrate to Typescript, but it would be great to have up to date d.ts typings for Fabric.

asturur commented 2 years ago

i was supposed to fill up the issue text. I ll do soon. We are looking for suggestion, we are all ears and we don't have strong opinions yet.

We would like to move away from legacy js anyway.

ShaMan123 commented 2 years ago

I am for TS (gave a read to what you wrote @jimmywarting, looks interesting). IMO the disadvantage of .d.ts files is maintenance. Because it is not part of the code it can quickly become outdated and irrelevant. Personally speaking I find TS most helpful in replacing the need for docs in my workflow. My IDE suggests whatever TS has to offer and my learning curve grows exponentially because of it. Bottom line, that's what won me over, besides the trivial (great 😅) advantages. Regarding the type nightmare - I understand what you mean and I don't deny, but I would expect the pain to reduce after the first adoption phase. I can counter with my pains from js. The first that come to mind - not knowing function signatures and having to guess, remember them or log arguments if the above fails. Or some minor typo that breaks my code and an error that sends me to a wild goose chase after the wrong thing. Is JSDOC typedef visible outside of it's file/module? If not it is an absolute killer to any kind of js typings. From the maintainer point of view I think it cuts down Q&A as well. So I'm for. I do think that a simple configuration file (tsconfig.json) in a project can silence ts for a dev that doesn't want it. And it is the standard as far as I can tell (saw many large repros migrate to ts, including google open source). I think that when considering this move we should try to foresee what is right for 1-2 years ahead, not only for today. In other words, we need to think what is best for fabric in the long term (and hopefully the short as well). I would like fabric to be easy. Easy to use, easy to learn, easy to plug and play, easy to contribute to, easy to maintain. That's what I want to achieve from the migration.

jimmywarting commented 2 years ago

IMO the disadvantage of .d.ts files is maintenance. Because it is not part of the code it can quickly become outdated and irrelevant.

this can be auto generated durning release...

I think that when considering this move we should try to foresee what is right for 1-2 years ahead, not only for today.

what would you think could happen when optional static typing comes into play? typescript will become obsolete.

saw many large repros migrate to ts

just b/c big companies did it dosen't mean it's better than one or the other, it just comes to a preferences and the developers background (like coming from c, rust, go or any other typed language)

there are ppl who have ditched typescript and went back to vanilla js afterwards (even in big companies too)

For me it feels like typescript are trying to fit a square into a circle, javascript is a dynamic language, and it's nothing wrong with that, it can be a positive thing also...

there are ways to write the code in such a way that auto completion and annotation isn't necessary from either jsdoc or typescript also. The public/private keyword and explicit return type for example are not needed, they are unnecessary, even for typescript. the only time you really need to explicit say what something returns is if you do something like:

function sleepFoo() {
  return new Promise(rs => {
    setTimeout(() => {
      rs('bar')
    }, 100)
  })
}

which don't have to be annotated at all if you did:

async function sleepFoo() {
  await sleep(100)
  return 'bar'
}

beside typescript don't do any type protection after you compile it to js. ppl can still make mistake afterwards. typescript only provide type hints/error upfront durning developing. And there is going to be ppl that don't use typescript at all

Gerwin-prog commented 2 years ago

@asturur will you also move away from supporting Legacy JS entirely & only support TypeScript and/or ECMAScript Modules?

kirill-konshin commented 2 years ago

Based on my experience, most of the projects can be migrated to TS with minimal effort (obviously, the types will not be very strict, some compromises will take place), but it's a proper foundation to improve these types in future.

TS can work even w/o types as plain JS, types can be introduced function by function, file by file.

Additionally, w/o TS project will have to support custom .d.ts files, which is a nightmare, it's much easier to convert code to TS and generate .d.ts automatically.

keelii commented 2 years ago

I am for TS too, because:

  1. old style javascript sometimes is hard to understand(even though you are expert on JS for years). prototype extends, contextual-this.., there is so much lines are hard to read in fabric.js
    fabric.Object.prototype._setLineDash.call(this, ctx, this.selectionDashArray)
  2. when i implement my custom object.class, the createClass method sometimes misleading people(ES 6 class looks more explicity), it hard to know which method should be implemented or overrided(the underscore method or not or both?)
    
    var MyClass = fabric.util.createClass(fabric.Object, {
    bar: [], // dont't do this
    initialize: function () {},
    })
    var c1 = new MyClass()
    c1.bar.push(1)
    var c2 = new MyClass()
    console.log(c2.bar) // => [1]

var MyClass = fabric.util.createClass(fabric.Object, { initialize: function () { this.bar = [] // do this instead }, }) var c1 = new MyClass() c1.bar.push(1) var c2 = new MyClass() console.log(c2.bar) // => []

3. there are some global variables in fabric namespace, which is hard to find(you have to read the source code to find it), with TS, just give me a interface, problem solved.
```js
fabric.disableStyleCopyPaste
fabric.copiedText
  1. when you built a large canvas-based project, the code style always OOP right? TS bring the full OOP support to JS, when i coding with TS, I feel confident, enjoyable. with JS i always confused, it too dynamic. in fabric.js the actionHandler in fabric.Control class is a absolute beautiful abstraction for object transform action, but when i involved in this part of code, it's hard to find where i am.
asturur commented 2 years ago

I updated the description. Probably the best option is to maybe convert a non working example of a class, to see how does it looks like in modern JS and TS. Maybe RECT that is a quick one.

jafferhaider commented 2 years ago

I'm for vanilla JS, because we finally have decent traction on modern features being implemented in ES. Looking at the long-term, I feel it would be better for FabricJS to stay with a maturing vanilla JS because many new features on the roadmap will make TS irrelevant anyway.

If we were having this discussion a half a decade ago, I would have been all for TypeScript.

kirill-konshin commented 2 years ago

Colleagues, main point about Typescript is it’s strong and flexible type system. No vanilla js (even with jsdoc) gives true type checking during compilation time. In all my JS projects migrated to TS, type checks found a number of potential issues that were overlooked.

Second reason is DTS (definition) files. Since we’re talking about library development, TS is a de facto standard. Definitions must be produced in any case, if we target large audience, which includes other TS projects.

Definitions on top of JS tend to have errors, mismatches and discrepancies, and require a lot of manual maintenance. Of course one can generate dts from jsdoc, but this will not do type checking… and in case JSDoc has issues those issues will silently leak into DTS, which means more maintenance efforts.

Third, TS is mature tool, with huge community, it’s safe and proven. Vanilla JS is catching up, it’s still years behind TS in terms of features. And even when it will catch up, vanilla won’t have types anyway, which brings us back to previous points.

PS. I forgot to mention, that most IDE are able to infer TS types even when they are not explicitly defined. TS compiler can do that too. This means less things to write, less things to support which in turn means less things that can go wrong.

jimmywarting commented 2 years ago

I don't consider any compile to js a "standard" language... It don't run anywhere without being compiled to js in the first place

It is built on top of standard js

jimmywarting commented 2 years ago

I like buildless setup a lot It loads so insanely fast 🚀 Having esm would be cool too

Only time when i actually use any build tool is if I need to down level anything for older environments

But all browser basically have auto update now and IIE it's pretty dead...

asturur commented 2 years ago

which are the functions of TS that are better than JS, types apart? i thought where identical. I wonder if we can get best of bot, we write in ts, and we can build into plain js and bundled legacy js

jimmywarting commented 2 years ago

I wonder if we can get best of both, we write in TS, and we can build into plain JS and bundled legacy JS

The only way you are going to get both of both world is if you write in JS and turn on checkJS to utilize all TypeScript features, you may even use typescript to bundle to legacy js form plain js jsdoc can do type checking

VScode has a nice interfear type from usage that helps u fill in the jsdoc when necessary, you can write interfaces (typedef) and import them with jsdoc as well...

asturur commented 2 years ago

yes my initial idea was that strong jsdoc could generate d.ts files. But i never found a proper tool to do it.

Isn't TS with ripped types just plain JS? There must be an option for the compiler, don't touch js no?

asturur commented 2 years ago

ok let's start to check checkJS

jimmywarting commented 2 years ago

Isn't TS with ripped types just plain JS?

not when you have target set to like some very old environment, it's always going to do some form of transformation to your code. 1000 of developers think they write es syntax with their extension-less imports but the reality is that it most often is compiled to cjs, the lack of extension create wasteful IO time to look for the correct file...

There must be an option for the compiler, don't touch js no?

alloJS? ignore, exclude, include... something something

kirill-konshin commented 2 years ago

@jimmywarting Your point "annoying sourcemaps, extra compile time, bloated code that looks nowhere near the actual input" contradicts with your other point "target set to like some very old environment, it's always going to do some form of transformation to your code" — you can't publish raw ESM code.

"all browser basically have auto update now and IE it's pretty dead" — Random old browsers still exist in corporate sector, where auto update may be turned off... Also don't forget government sector. You still have to compile CJS and maybe even ES5. Obviously we should not target IE6 or IE7, but only targeting latest Chrome is another extreme.

Fabric is a very popular lib, most popular based on stars, it cannot throw away compatibility, so there WILL be a build/compile script, one or the other.

image

Competition is tough...

VSCode does have a good support for JSDoc, but I've never seen a reliable tool to check JSDoc types on CI, whereas TS compiler will do it during the build as normal part of CI process.

@asturur wrote:

which are the functions of TS that are better than JS, types apart? i thought where identical. I wonder if we can get best of bot, we write in ts, and we can build into plain js and bundled legacy js Isn't TS with ripped types just plain JS?"

This is true, for the most part JS now is TS without types, except a few features like decorators (which Fabric may use as strong-typed mixin replacement). When TS is used you get types for free, DTS for external consumption.

jimmywarting commented 2 years ago

you can't publish raw ESM code.

I could if i wanted to. I can code and develop in raw ESM, making sure it run just fine in latest chrome and maybe firefox, without having to compile anything. And when i want to make a release then i can run some test that builds some UMD, test it in several browsers and releases both ESM (maybe as is - for those who wish to simply import() it) and also release the UMD version for the rest.

it's possible to use webpack, rollup or tsc or whatever.

kirill-konshin commented 2 years ago

@jimmywarting

I could if i wanted to. I can code and develop in raw ESM, making sure it run just fine in latest chrome and maybe firefox, without having to compile anything. And when i want to make a release then i can run some test that builds some UMD

Yeah, you can do that, but you trade compilation time during dev at expense of testing and verification time. And again, you won't have to support DTS files separately... It's always a tradeoff. So unless you actually ship raw ESM I don't see practical benefits. I advocate for "Eating your own dog food" to be sure I am seeing what others will see, as close as it can be. And as early as I can, ideally during development, not verification.

Compilation time in watch mode of a library the size of fabric is few seconds. It's not a big deal. But then during development you can be sure the code you see is exactly what will be shipped.

To me, when you develop a LIBRARY — TS is a no brainer choice. When you develop an APP — your approach may be OK.

Also there are rust (SWC) or Go (esbuild) based compilers that are even faster than regular TSC/Babel/Rollup/etc., and looks like it's the general direction where industry is moving, Next.js used to be a JS project, and they did full transition to TS, it's just one example... overall the trend is towards TS in lib development.

Overall the choice should be based on if team will be able to deliver faster and better quality with TS or with JS + custom DTS. From my experience in multiple teams and products, TS helps to speed up overall delivery if all factors are considered (development, dev experience, CI, QA, support).

ShaMan123 commented 2 years ago

I think it is clear that fabric should not and cannot deal with the build process. It is unwise, to say the least, and a damn waste of effort. The effort should be targeted towards fabric and making it awesome. Let everyone do what they do best. And of course fabric must use a liable mechanism, no alpha/beta products. Too much headache in something that no one should have to manage except the occasional ci config. The patience to except alpha/beta and the occasional break/bug is ONLY intended for fabric features. We cannot abuse that. Already I argue it is abused with no need. I mean to say the decision needs to be mainstream. And it MUST make this repo easier and readable! Because it isn't! I am an expert in fabric internals not because I want too. But because I had no choice but to dig in the source code and break my head against it. What other choice is there today?? Why should anyone want to work with a repo that demands so much just to get some minor customization working? It's too hard, messy, horrible DX. The point is taking fabric forward, not drowning it with more noise.

asturur commented 2 years ago

Yes but indeed there would be 2 targets eventually. One to get modern JS out of TS, another to have the standard browser ready lib

asturur commented 2 years ago

I think it is clear that fabric should not and cannot deal with the build process. It is unwise, to say the least, and a damn waste of effort.

Well we don't get away without publishing a ready to consume module. Is what npm user expect. You don't build stuff that is in your node_modules folder usually. You expect it to be ready to use. If you want to use the modules right away you usually import them from the src directory. There is not just a single way to do things of course, but i m sure we have to build.

CaptEmulation commented 2 years ago

Types would be amazing whether provided by declaration files or an actual Typescript port.

https://jimmywarting.github.io/you-might-not-need-typescript/ is interesting, and does make valid points. However, here I stand in a sea of Typescript projects wanting more Typescript :-)

ShaMan123 commented 2 years ago

I think it is clear that fabric should not and cannot deal with the build process. It is unwise, to say the least, and a damn waste of effort.

Well we don't get away without publishing a ready to consume module. Is what npm user expect. You don't build stuff that is in your node_modules folder usually. You expect it to be ready to use. If you want to use the modules right away you usually import them from the src directory. There is not just a single way to do things of course, but i m sure we have to build.

Sure, we have to build. But with minimum effort. Let the build tool do all the work, that's what I was aiming to stress.

andrewzolotukhin commented 2 years ago

I think building is something we should avoid for Fabric. But we can definitely move towards ESM. I would not use TS, instead of it I'd create a d.ts files and add typings through JSDoc if needed.

folknor commented 2 years ago

For what it's worth, I want to voice my thoughts:

kirill-konshin commented 2 years ago

They do not provide their own TS definitions, they are maintained separately here

I worked a lot with this project. Separate declaration is one of the reasons why bugs occur because of discrepancies. And a need to do coordinated merges, which is painful. It is a very bad approach. Types should be shipped along with library.

The resulting compiled JS is committed to git when they do a release

This is also considered as bad practice, build artifacts should not be part of version-controlled repo because it leads to horrific merge conflicts.

kirill-konshin commented 2 years ago

Here's an example that illustrates why Fabric has to be written in TS:

http://fabricjs.com/docs/fabric.Object.html#forEachControl https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/fabric/fabric-impl.d.ts — does not even mention this method forEachControl

And I doubt JSDOC to TS types generator will be a good solution: it is awkward and does not give all benefits that TS can provide.

ShaMan123 commented 2 years ago

@kirill-konshin I have a huge patch file of types for fabric on top of the npm types module. I agree it is bad.

asturur commented 2 years ago

i posted on defintely typed if someone wanted to help build official d.ts file to keep here, but i got no one to volunteer. i m all for types comeing from here, the only thing at stake is between full TS and JS + .d.ts

asturur commented 2 years ago

@folknor release 5.0 is around the corner. I'm looking for the small last fixes to pull in.

ShaMan123 commented 2 years ago

see the ts proto - very simple, nothing fancy, minimal effort (promises included with a bit of a more advanced type related to the function), proper js class: https://github.com/ShaMan123/fabric.js/blob/ts-proto/src/shapes/Rect.ts

run npx tsc or npm run tsc after you've updated dev deps. It will output a readable js file next to the ts one. Give it a go.

ShaMan123 commented 2 years ago

https://www.typescriptlang.org/#adopt-gradually

Devil7DK commented 2 years ago

I'm also for TS. Most of the things I wanted to say to @jimmywarting's arguments @kirill-konshin already said it. And I'll gladly help if you decide to migrate to TS.

kingschnulli commented 2 years ago

My main concern currently is that there is no tree shaking or real modules. For the TS/ES6/Plain or whatever point, we have babel so there is no reason to not just write everything the way @asturur and the other maintainers want. But still, will give +1 for TS because this will help everybody in the long way.

asturur commented 2 years ago

With those changes we would have proper modules, and custom build would go away.

kingschnulli commented 2 years ago

Whatever you will decide, let the contributors know how we can help in getting things done - I think there are quite a bunch of people willing to help for this transition.

asturur commented 2 years ago

So for example, help me figuring out, what is the best transpiler for library? i work on apps and there webpack + babel is a standard, but i don't think is for libraries. Can TS do transpilation and bundling all together?

kingschnulli commented 2 years ago

I'm using webpack to build a library which bundles current fabricjs and our custom vuejs code - so this is doable. But I think there is a lot going on with esbundle, swc, parcel etc. currently which might be a better solution. Maybe someone with more experience can chime in here and make a good proposal what bundler solution is the best for fabric.

kirill-konshin commented 2 years ago

You can use TSC compiler to build the library.

If you want to also publish the final build like this one https://cdn.jsdelivr.net/gh/fabricjs/fabric.js@vv6/dist/fabric.js you can do an extra step and compile it using Webpack with SWC loader (which is the fastest way to compile TS without types, since you don't need types in Webpack bundle).

Here's how I did it long time ago: https://github.com/ringcentral/ringcentral-js/blob/master/sdk/package.json

3 builds: ES5 for oldies, ES6 with tree-shaking for modern tools, UMD (webpack) for direct consumption.

andrejslogins commented 2 years ago

I'm also using fabric in the project I'm at currently, and personally, would love to see typescript used. Especially since now, I'm investigating some internal issue that feels like a race condition causing some of the variables being undefined when being accessed

yassilah commented 2 years ago

I personally also would love to see TS support. I have made a few (small) contributions to this repo and I found it quite hard to navigate the codebase without reliable signatures for complex methods. And it would really be my pleasure to help migrate to TS if there's anything you need 🙂

Lazauya commented 2 years ago

I'm definitely down to help work on the migration too; I just found a pretty big discrepancy between the DefinitelyTyped types and the actual lib, and so I think that migrating to TS would be pretty awesome.

Lazauya commented 2 years ago

So the question is, how do we want to approach this? Should we first use a tool to generate initial types and slowly fill stuff in from there?

Also, what should we do about the "Klass" stuff? Do we want to gut that and replace it with real modern classes?

ShaMan123 commented 2 years ago

So the question is, how do we want to approach this? Should we first use a tool to generate initial types and slowly fill stuff in from there?

Any experience with reliable tools?

Also, what should we do about the "Klass" stuff? Do we want to gut that and replace it with real modern classes?

Absolutely! We have been preparing fabric for it (#3684 )

Lazauya commented 2 years ago

Any experience with reliable tools?

Unfortunately no. I'm going to do some research to see what's available. Ideally a tool to add simple types like string, bool, etc. Manual typing could be done for the fabric objects. I'm planning on creating a branch with the basic types added from whatever tool I end up using and then going from there.