-
Notifications
You must be signed in to change notification settings - Fork 3.5k
TypeScript support #4232
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
TypeScript support #4232
Conversation
|
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). 📝 Please visit https://cla.developers.google.com/ to sign. Once you've signed, please reply here (e.g.
|
|
I signed it! |
|
Thanks, this seems like it would be a useful addition! Since TypeScript mostly overlaps with JavaScript, this would be better an an option to the existing JavaScript generator (in IDLOptions), rather than a copy of the existing generator (since now we have have to maintain 2 of them). Also please add some description to your PR? |
|
I based this PR on previous work in #4065 where TS and JS were merged in one file. This resulted in a lot of if (ts) statements, not to mention some other problems (exports for js were unneeded for ts; missing imports for ts, while they're unneeded for js; missing namespaced external dependencies for ts, etc.). That's why I decided to separate those two. |
|
CLAs look good, thanks! |
|
Well, yes, you'd have to factor the code such to keep the amount of if-thens at a minimum. But sharing the code still seems very useful to me. |
|
Ok, so should I merge them? |
|
Yes, please do. |
fasterthanlime
left a 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've left a few comments regarding some style issues / opportunities for deduplication, overall I'm really excited about this PR landing :)
src/idl_gen_js.cpp
Outdated
| if (IsEverythingGenerated()) return true; | ||
|
|
||
| std::string enum_code, struct_code, exports_code, code; | ||
| ImportedFileSet importedFiles; |
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.
as per the google C++ styleguide, importedFiles => imported_files
src/idl_gen_js.cpp
Outdated
| : BaseGenerator(parser, path, file_name, "", "."){}; | ||
| const std::string &file_name, bool ts) | ||
| : BaseGenerator(parser, path, file_name, "", ".") | ||
| , ts_(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.
Instead of passing a ts boolean, I recommend using the same approach as idl_gen_general and use an array of structs: here's one way to do it
src/idl_gen_js.cpp
Outdated
| return path + file_name + "_generated.js"; | ||
| const std::string &file_name, | ||
| bool ts) { | ||
| return path + file_name + ((ts) ? ("_generated.ts") : ("_generated.js")); |
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 opt for the JsLangParams array of structs, it's easier to have the third parameter be a const JsLangParams & and use lang.file_extension here
src/idl_gen_js.cpp
Outdated
|
|
||
| // Output the main declaration code from above. | ||
| if (ts_) { | ||
| code += import_code; |
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.
for non-typescript, wouldn't import_code be the empty string anyway? it seems like the surrounding if could be removed, making the += unconditional
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.
Yes, it would be ignored, but then there's no point in generating it in the first place. As the old saying goes - some work is more work than no work :)
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 pretty sure appending an empty string in C++ is very close to "no work" :) Besides it's not in a loop or anything (happens at most once per module) so it's not performance critical (not that flatc is in the first place). I still suggest removing the condition to remove "branching on type" (see anti-if campaign).
That way, if a third javascript-like language is added, the condition doesn't have to change, import_code will contain what's needed!
src/idl_gen_js.cpp
Outdated
| code += struct_code; | ||
|
|
||
| if (!exports_code.empty() && !parser_.opts.skip_js_exports) { | ||
| if (!ts_ && !exports_code.empty() && !parser_.opts.skip_js_exports) { |
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 seems like a good candidate for a boolean in the JsLangParameters struct (explicit_exports for example, true for js, false for ts)
src/idl_gen_js.cpp
Outdated
| const auto basename = | ||
| flatbuffers::StripPath(flatbuffers::StripExtension(file)); | ||
| if (basename != file_name_) { | ||
| code += "import * as "+ GenFileNamespacePrefix(file) + " from \"./" + basename + "_generated\";\n"; |
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.
since it's used here again, _generated has become a magic string - if changed in one place it'll break in the other, so it's a good candidate for a constant instead
src/idl_gen_js.cpp
Outdated
| void generateStructs(std::string *decl_code_ptr, | ||
| std::string *exports_code_ptr) { | ||
| std::string *exports_code_ptr, | ||
| ImportedFileSet &importedFiles) { |
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.
importedFiles => imported_files
| exports += "this." + *it + " = " + *it + ";\n"; | ||
| if (ts_) { | ||
| if (it->find('.') == std::string::npos) { | ||
| code += "import { flatbuffers } from \"./flatbuffers\"\n"; |
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 original PR also has this import emit in a loop, but I wasn't able to figure out why: it seems to be that it only needs to be emitted once?
also, to make the code self-documenting, it might be worth it to assign it->find('.') == std::string::npos to a local bool variable that indicates what that condition is in fact testing
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.
speaking of flatbuffers.ts, I don't see it anywhere in the PR, is that normal? am I missing something? is it also supposed to be generated by flatc?
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 issue here is that flatbuffers has a .js import but no .d.ts file. I have one locally, and there are more floating around on the web, but I don't know if I should include it in this PR. Probably so. Nevertheless, that's why the flatbuffers import is optional.
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 see. I suppose either shipping flatbuffers.ts, or a flatbuffers.d.ts is acceptable. The latter is probably superior since it would avoid code duplication between the javascript & the typescript versions?
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.
Yeah, having one of them is really a must. .d.ts seems more practical as you said. If nobody has any objections, I'll add the one I'm using.
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.
On the other hand, it would be better to submit it to Definitely Typed.
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.
DefinitelyTyped
or just a @types/flatbuffers package on npm, see https://www.npmjs.com/~types
| const std::string &file_name) { | ||
| return MakeRule(parser, path, file_name, true); | ||
| } | ||
|
|
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.
With JsLangParameters, these can be deduplicated, here's one way it can be done.
|
Thanks for all your comments. I'll add more changes as soon as possible. |
|
@aardappel would it be possible for you to take a look at this PR again? Still a bit if-happy but I believe it addresses most of the points in the original review of #4065 |
| *annotations += "} " + nameprefix + field.name + "\n"; | ||
| *arguments += ", " + nameprefix + field.name; | ||
|
|
||
| if (lang_.language == IDLOptions::kTs) { |
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 if-then can be simplified I think :)
|
This PR looks pretty reasonable to me now. It appears to already have absorbed some changes from #4244. @fasterthanlime are you happy with the state of this PR? |
Yep, I reviewed the code and @krojew was reactive in incorporating that feedback into the PR! Ifs apart, maybe the last remaining point is testing - it'd be nice to at least check that the generated .ts validates with the typescript compiler, but that would require a full-blown node.js runtime on Travis & AppVeyor - does the current CI setup already support that? |
|
I'm actually using generated TS files for my project (almost 100 .ts message files, with lots of includes/imports) and it works fine. TS is used with noImplicitThis, noUnusedParameters, noUnusedLocals, noImplicitAny and noImplicitReturns. |
|
TS typings just landed in Definitely Typed (so they will be available in @types soon). |
|
You're right - Long is problematic. In my project, I solved it by simply not using numbers, where Long is expected, and passing Longs all along. And there's this hack: Nevertheless, this is a topic for another PR. |
|
@krojew The output from translating TS code shouldn't overwrite the JS code, and it shouldn't be in this commit. We want to be able to track changes to JS code seperately. |
|
ok, done |
|
Thanks! I can merge, if there are no further comments. |
|
A couple of issues when running this locally.
|
|
Unfortunately, you need to be able to run npm since the test needs flatbuffers typings (and the latest version, which doesn't seem to be merged yet). Otherwise the compilation will fail. |
|
Why? the JS test runs locally, and it be great to do that for TS too. |
|
I can refactor the JS and TS tests to be more independent. Note that not using npm requires us to include flatbuffers typings in the source and manually update them every time those change. Is that really what we want? |
|
Not being a JS/TS user, I guess I don't understand the requirements. Doesn't TS already have types in the source? Why do they need to be external, and why do they need to be retrieved through npm? |
|
When dealing with external JS libraries, TS doesn't know anything about what's inside. That's why it needs a helper declaration file, usually with a .d.ts extension, which tells the TS compiler what is really contained in a given JS lib. For flatbuffers, I've made such declaration, and it's available here: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/flatbuffers/index.d.ts |
|
So why don't we also have that file in this git repo? If people make changes to the TS implementation, which presumably also changes the |
|
The standard place for typings is the @types repository which fetches files from DefinitelyTyped. Storing one locally here would mean synchronizing it with DT, so other people could get it the standard way. I think it's more practical to update it once in DT and make the test fetch it from @types. That's the typical workflow for TS projects. |
|
So to make changes to TS, you have to make a PR to 2 repositories? Seems inconvenient to me, but if that is standard.. I guess so. Either way, maybe you can address my other 2 suggestions above (the non-npm ones). |
|
It might be inconvenient to library authors, but it's quite convenient to the users - all that's needed to use flatbuffers in TS is: |
|
@krojew Thanks for you work! |
|
@liukun thanks.. I was wondering that too. |
|
@liukun most of JS libs I've seen have typing in @types, which is really convenient for the users - both the lib and typings are kept in sync via npm, and there's no need to ping-pong between npm and library repo. In fact, this is the second time I actually see something not publishing its typings to @types. |
|
Why does it attempt to import flatbuffers from './flatbuffers' which assumes a relative location, rather than just 'flatbuffers' which would allow us to module resolution? Ex: npm install --save flatbuffers => import { flatbuffers } from 'flatbuffers'; |
|
@JoshGalvin I'm no JS/TS expert, but this may well have been inherited from the JS implementation. There were at least 2 attempts before (one is here: #3750) to address this, but the last had to be rolled back due to incompatibility with some environments I seem to remember (Google Closure compiler)? |
|
This import is a leftover from a time where there was literally nothing TS-related for flatbuffers (from the first PR). You can disable it with a --no-fb-import flag. |
|
I want it but just with 'flatbuffers' since I'm using node and webpack for module resolution. I made a fork with the change but yeah I saw that it gets reverted when people attempt that pull request. I can dig into the reasons and get it working w/ the errant environments. :sadness: The namespacing is super weird when using them as modules too since everything would be exported on whatever namespace you provide (and broken if you don't provide a namespace). import { Namespace } from './transform-generated';
Namespace.Transform.etc..vs import { Transform } from './transform-generated';Edit: An alternative approach to generating TS would be generating the .d.ts files for the javascript version. This would give type information to javascript users in modern IDEs and allow typescript users to use them directly. |
|
The problem with having it just be 'flatbuffers' is that it forces users to be mindful of which version their If they generate flatbuffers glue with a flatc version that's more recent than whatever their |
|
That'd definitely meets my needs. The only thing better would be bundling flatc with the npm module like http://casperjs.org/. It attempts to build the binary and falls back on grabbing it if that fails. Edit: |
Thank you for submitting a PR!
Please make sure you include the names of the affected language(s) in your PR title.
This helps us get the correct maintainers to look at your issue.
If you make changes to any of the code generators, be sure to run
cd tests && sh generate_code.sh(or equivalent .bat) and include the generatedcode changes in the PR. This allows us to better see the effect of the PR.
If your PR includes C++ code, please adhere to the Google C++ Style Guide,
and don't forget we try to support older compilers (e.g. VS2010, GCC 4.6.3),
so only some C++11 support is available.
Include other details as appropriate.
Thanks!