-
Notifications
You must be signed in to change notification settings - Fork 239
TypeScript migration #145
TypeScript migration #145
Conversation
Hi @alcuadrado, great that you are tackling this, this looks already great! 😄 |
|
Thanks for your reply @holgerd77
I went for the first option, as it plays better with tools like ides and typedoc.
That was my first thought. But I'm not quite sure about it. @krzkaczor do you have any opinion on this? This PR is almost ready. I tried not to introduce breaking changes, but there are two things I'm not completely sure how to handle:
The current version of this library has four ways of importing it:
Special configuration (i.e. different from ethereumjs-config's) is needed to preseve the last three options. I didn't implement such configuration. I'd like to know your thoughts about this first.
I used the same migration approach than
Javascript users won't notice this, and their code won't break. Typescript users may have to introduce a few Note that constructors aren't affected by this. Should we keep it this way? Finally, I tried to run the tests on firefox with karma and typescript. I'm getting an error about the Is there a typescript project with browser tests I can look at? Notes for reviewers:
|
Also, I replaced some instances of this pattern with default parameters. They are not 100% equivalent. Default parameters only handle Should we consider this a breaking change and revert it? |
On 1., Importing The first two "es5" import options shouldn't be used at all, so it would be very good if we get officially rid of them on this occasion. If we change Fake transaction import, so be it. My preferred way would be that we move the content from index.ts to a separate transaction.ts file, use index.ts just for exposing the API and then have exposure and import structure on usage similar like in this PR on the VM: ethereumjs/ethereumjs-monorepo#496 |
|
(sorry, I would love to better format my posts, but I'm always writing on mobile these days and this gets pretty difficult) |
This has already been addressed in the testing library (by removing the dependency). I still have to do a release on this, then you can directly update the ethereumjs-testing dependency, should happen during this week, will let you know. |
I like this idea. Will update the PR with this.
Great, that's one of the few things missing here. |
Re field assignment: I think it should be fine to restrict it define the types to be Buffer for TS users. We'll eventually want to restrict it even for JS users, and as you mentioned TS users will only get a compile-time error, and runtime allows the other types. Just had a look, this is looking great! By the way, do you by any chance know the purpose of FakeTx? |
TBH I have no idea why FakeTransaction exists. There's not much data about it in its git history, but most of the people there are part of the Truffle/Ganache team. Maybe they use it in Ganache? |
@alcuadrado Ok, just released v1.2.8 on For context: this is always just done as a tagged release and not published on npm due to npm package size limitations (test submodule is too big). So library is just referenced by its version tag. |
|
This comment has been minimized.
This comment has been minimized.
Let's leave it like that then.
Thanks for the release and the explanation. I was very intrigued about the git reference in the I'm still having errors that prevent me from running the tests in a browser. I installed https://www.npmjs.com/package/karma-typescript, which seems like the most popular way of using Karma with TS. This project doesn't support ES6, so it fails to load Is any other ethereumjs project running tape tests in the browser? |
The latest karma test activation was on the VM https://github.com/ethereumjs/ethereumjs-vm/blob/master/karma.conf.js, on the v4 branch this is also running in a TypeScript context, maybe you can get some inspiration from there. |
In VM (v4) typescript files are first built and karma is run on the dist files. |
Thanks for pointing that out @s1na. I just updated this PR. It builds the tests before running them now. I couldn't get the official tests to run in a browser. I'm pretty sure The last standing thing before considering this ready for review is this:
|
Small update: I reapplied #138 here manually, as so many things changed that rebasing was harder. Is this ok? |
@alcuadrado ah sorry, but rebasing is an absolute inviolable precondition for having a PR like this merged, especially if the code changes building on top are so heavy. |
(but maybe I'm also just not completely understanding what you have done, how would this actually work during merge?) |
Hmm, I wouldn't see this default parameter thing as too problematic as I am just not seeing the case one could have misused this or this triggers an error. Maybe I am missing something though. For the moment I wouldn't regard this as breaking. |
You are right @holgerd77, it would have been a mess. I rebased it.
The only case that comes to my mind is if someone passes |
This was used in previous versions of this library to export ES5-compatible versions of the code. This is no longer needed.
#143 is merged now. I rebased this PR against master. |
Hi @s1na, I am still a lot more reduced in time than I hoped for. Could you eventually do some side-path and do the final review here? Or is this too distracting from your current VM work (really don't feel obliged, would be appreciated though, maybe that's also some nice dive into this library)? |
Yeah sure, I had in mind to help with the review here. Will try to do an initial round shortly. |
Short update: I integrated this into VM and apart from changes needed for imports, it runs and the tests pass! this is already a good sign. Will go through the code in more detail. Just one quick comment regarding: I'm not sure I understand the benefit of the |
That's great :) How did you do it?
I like naming types for documentation purpose. I'm also ok with using just |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good PR overall, thanks @alcuadrado :) Just a few minor questions/points.
@@ -2,16 +2,25 @@ | |||
"name": "ethereumjs-tx", | |||
"version": "1.3.7", | |||
"description": "An simple module for creating, manipulating and signing ethereum transactions", | |||
"main": "es5/index.js", | |||
"main": "dist/index.js", | |||
"types": "dist/index.d.ts", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably also needs files
to point to dist/**/*
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
this._common = opts.common | ||
} else { | ||
const chain = opts.chain ? opts.chain : 'mainnet' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@holgerd77 Is this maybe also a good occasion to drop some deprecated options? namely opts.chain
and opts.hardfork
which are now covered by opts.common
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would simplify the constructor a lot. I don't know how much of an impact this change would have though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These Common
and chain
/hardfork
options were introduced in parallel along with the the Common
class introduction. Reasoning is to be able to use a shared common class more in a programmatic/dynamic context as well as keep a simple option for users who just want to instantiate e.g. a chain:goerli
tx without having to deal with the extra Common dependency.
Not sure if this is ideal but this has been introduced along all libraries using the Common library as a shared pattern so for now I would like to stick with that if there is no strong reasoning against.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, sorry I thought they were for backwards compatibility. No I don't feel very strongly about this.
if (chainId < 0) chainId = 0 | ||
|
||
// set chainId | ||
if (opts.chain || opts.common) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry this is not directly related to your PR, but could you maybe add a short note to the tsdoc that explains the order of priority for chainId? if I'm not mistaken its 1. opts.chain || opts.common.chainId()
, 2. chainId from signature and lastly data.chainId
4. defaults to 0! Not sure why so complex.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll document it if we decide to keep it. I agree that it's too complex.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
//cc @danjm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See related comment from @danjm here: #131 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand this has been to avoid breaking changes and for the convenience of the user. I do think however that it makes it very easy for the user to make mistakes given that a single value can be set from 4 sources in an arbitrary order. What I propose:
Make common
a mandatory option, remove opts.chain
, data.chainId
and throw if common.chainId()
doesn't match the one from signature (if present). Making common
mandatory can also allow us to deprecate chain
/hardfork
and allow users to migrate.
That said, I don't feel very strongly and this was simply my (relatively uninformed) opinion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alcuadrado what do you think? We can address this in a separate PR though but whatever eases the process.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if making common mandatory is the best, as users that aren't familiar with it will find it confusing.
One option is to throw whenever there are incompatible sets of options. E.g. if you pass it common
and chain
, throw despite their values.
I opened #149 to continue this discussion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you pass it common and chain, throw despite their values.
Just a note that this is already done here: https://github.com/ethereumjs/ethereumjs-tx/blob/master/src/transaction.ts#L70
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, then let's move on here with keeping Common
and alternatively chain
/hardfork
passing "as is" together with the throw mechanism on incompatible options @danjm mentioned. Otherwise we'll implement what Sina proposed above:
Make
common
a mandatory option, removeopts.chain
,data.chainId
and throw ifcommon.chainId()
doesn't match the one from signature (if present). Makingcommon
mandatory can also allow us to deprecatechain
/hardfork
and allow users to migrate.
And we then access chainId
solely through the passed or generated Common
instance.
Does this make sense?
What is with this 0
default value. Is this necessary respectively does it make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alcuadrado Can you prepare a PR on this once we have an agreement?
* Returns the rlp encoding of the transaction | ||
*/ | ||
serialize(): Buffer { | ||
// Note: This never gets executed, defineProperties overwrites it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I think I had also added this to ethereumjs-account
. But I think you're right that it gets overwritten by defineProperties
. In that case would there be a reason for keeping it (apart from documentation)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's needed for TS. You wouldn't be able to call tx.serialize()
otherwise, as it won't be present in the Transaction
type.
We may be able to do declare it as a field, something like serialize!: () => Buffer
. Not sure what's more confusing.
We probably want to do that for toJSON
. Otherwise, we'll have to duplicate a lot of logic.
validate(): boolean | ||
validate(stringError: false): boolean | ||
validate(stringError: true): string | ||
validate(stringError: boolean = false): boolean | string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this last definition necessary for typescript? Don't the first three definitions cover all cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is something tricky, but really useful, about typescript. When you are overloading functions/methods, the last one is not exposed. It's just the implementation of the preceding overloads.
This may look weird initially, but it lets you expose very clean overloads. If the implementation were exposed, there will always be a weird overload.
}, | ||
singleRun: true, | ||
plugins: [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These aren't needed anymore? in that case maybe we can also drop their dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just the declaration that isn't needed. I had to update karma, and the newer version autoloads plugins AFAIK.
@@ -24,26 +33,34 @@ | |||
"ethereumjs-util": "^6.0.0" | |||
}, | |||
"devDependencies": { | |||
"async": "^2.0.0", | |||
"babel-cli": "^6.22.2", | |||
"babel-preset-env": "^1.6.1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The .babelrc
file can also be removed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! :)
"prepublishOnly": "npm run test && npm run build", | ||
"coverage": "ethereumjs-config-coverage", | ||
"coveralls": "ethereumjs-config-coveralls", | ||
"docs:build": "typedoc --out docs --mode file --readme none --theme markdown --mdEngine github --excludeNotExported src", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way, what do you think about --gitRevision master
for the docs? see ethereumjs/ethereumjs-util#181 for context
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have mixed feelings.
I think it'd be good to keep the docs pointing to their corresponding commit, but we are regenerating them manually, and they can easily get outdated.
Most people will get to them through the repo, so pointing to master will be right in the majority of the cases. It will only be wrong when navigating a specific version.
I guess the best solution would be for some service to regenerate them on every push/merge. Something like netlify. But that'd mean hosting them somewhere else.
Ok. Then let's merge here. 😄 |
Damn, sorry, maybe this was too rushed. I was not aware any more that this PR was not pointing towards Sorry for the mess. |
This is a WIP PR migrating this project to TypeScript. It is heavily inspired on ethereumjs/ethereumjs-account#27. In particular how the code was migrated and which tools are used were copied from there.
This PR branches off #143 as I wanted to have an updated version of the official tests for this migration. That update was made in #131, but #143 fixes an error introduced by it.
Some things were I'd love some early feedback are:
any
and still support the whole range of values that the released version supports. Is this OK, or should the new version be more restrictive for TS users?ethereumjs-util
? Many of them are related todefineProperties
.@types/bn.js
as a dev dependency becauseethereumjs-util
andrpl
's typing files import them and don't declare it as a dependency. This doesn't feel right to me.