flatbuffers icon indicating copy to clipboard operation
flatbuffers copied to clipboard

[TS/JS] Entry point per namespace and reworked 1.x compatible single file build

Open bjornharrtell opened this issue 3 years ago • 12 comments
trafficstars

Implements reworked entry point generation, so that each namespace gets an entrypoint and re-exports children. The top level entry point can be bundled up into a single file that should be 1.x compatible. This also has the benefit of avoiding name clashes which occured in the IMHO not so well thought out previous attempts (including my own) to export everything in a single module at root level. A new option is added --ts-entry-points to enable the entry point generation which also implicitly also enables --gen-all because it needs all symbols for the entry points.

Note that this PR also rolls back the change to the npm package entry point name. It was changed to js/index.js some time ago but is now back to js/flatbuffers.js which it was before (also in earlier 2.x versions) because IMHO that was an unnesseary breaking change. Specifically this makes it possible to run the old test suite from flatbuffers 1.x which is added back as tests/ts/JavaScriptTestv1.cjs and it works with no other modification than moving from flatbuffers.Long to native BigInt support which was was a breaking change we made years ago. One could argue we should have kept flatbuffers.Long for backwards compatibility but that ship has sailed and probably not worth the effort anyway IMHO.

The previous implementation of option --ts-flat-files has been replaced by esbuild automation. It simply automates runs of esbuild to produce old style 1.x generation single file bundles that assume flatbuffers is available in global scope.

This PR also removes some left over older generated TS/JS files missed in https://github.com/google/flatbuffers/pull/7508.

References https://github.com/google/flatbuffers/issues/7448.

TODO

  • [x] Handle reserved words (currently not replaced on re-export)
  • [ ] Check for esbuild availability when --ts-flat-files is used
  • [ ] Fix/update flatc_ts_tests.py
  • [ ] Handle case with root namespace table with same name as fbs

bjornharrtell avatar Sep 05 '22 22:09 bjornharrtell

I've commited a POC of bundled unminified root level entry point for monster_test.fbs at https://github.com/bjornharrtell/flatbuffers/blob/f046ab497d1030869ecb921607046fee9297ff99/tests/ts/monster_test_generated.js. I've not tested it but it looks ok. It should work with a globally imported flatbuffers and will define a global object monster_test which has the same structure as the flatc from 1.x generated single js file which means that for example monster_test.MyGame.Monster exists as a symbol.

It was generated using esbuild, with the following command line:

esbuild monster_test.ts --bundle --outfile=monster_test_generated.js --global-name=monster_test --external:flatbuffers

Note that by removing --external:flatbuffers flatbuffers is included in the bundle to create a fully stand alone file.

@dbaileychess this is essentially what https://github.com/google/flatbuffers/issues/7485 intends to resolve.

bjornharrtell avatar Sep 05 '22 22:09 bjornharrtell

To verify this is actually 1.x compatible I've added the test suite from latest flatbuffers 1.x version in https://github.com/google/flatbuffers/pull/7510/commits/7c96cab51f029c33b4ab0950237e9b577a76f66e and modified to handle the change to tests/ts path and that flatbuffers 2.x no longer supports the custom Long type and instead relies on native BigInt. And against the single file built monster_test_generate.js it passes for me locally (ran it with node JavaScriptTest1.js ./monster_test_generated.js).

bjornharrtell avatar Sep 06 '22 18:09 bjornharrtell

I've pushed through with this and moved it out of draft for review. It produces valid single file output that is 1.x compatible for the schemas in tests/ts. However, the flatc ts tests failures remains unresolved. I remain convinced what this PR does is worthwile goal and less hacky/quirky than what it replaces so that we only have two kinds of outputs instead of four (!). What I mean with this is that counting 1.x and 2.x current possible JavaScript/TypeScript output you can get 1.x compat single file builds, my initial attempted "flat" namespace entry points, your (@dbaileychess) initial --ts-flat-files and the "modern" individual modular output introduced with the TS rewrite. With this PR we can go back provide only 1.x compatible single file builds and the individual modular output which is what I see as the big win here.

While I do agree with the general idea of the flatc tests and what their purpose is I remain unconvinced the flatc ts tests provide real value because of the contrived schemas and that even the current implementation in master does not produce something that is valid JavaScript or TypeScript from such schemas.

@dbaileychess I sincerely hope we can discuss or move forward with this in some way. I've put many many hours of effort into this PR.

bjornharrtell avatar Oct 05 '22 20:10 bjornharrtell

I'm approving this. Please update the conflicts and I can prioritize.

I will take responsibility of fixing internal Google users of this. My plan is to release a version before I merge this in, merge that into Google, and then release this in its own version, so I can miminize the scope of changes. Sounds like a plan?

dbaileychess avatar Nov 11 '22 02:11 dbaileychess

Thanks that is great news to me @dbaileychess and I'll try to assist if you get stuck. I'll get this rebased ASAP.

bjornharrtell avatar Nov 11 '22 12:11 bjornharrtell

Rebased but new tests for arrays_test_complex schema fails due to what seems that my implementation doesn't properly consider "prefix" (-o parameter). I'll need a bit more time to fix that, hopefully sooner than later.

bjornharrtell avatar Nov 11 '22 12:11 bjornharrtell

I believe I've have the -o param / outputpath handling fixed up now.

One detail to note is that the single file bundles created when using --ts-flat-files is JavaScript - no TypeScript equivalent are produced. I don't see a use case for a TypeScript bundle and don't know of any sensible means of creating it.

I noted one remaining known issue with the generated bundles which is that the object API is not available (not exported). Not sure how important this is at this point, but should not be too hard to fix. @dbaileychess do you feel it's blocking this or that we can track it as a separate issue?

I also noted an issue with array_test_complex but is AFAIK unrelated to this PR (faulty --gen-mutable code, just disabled it for now). I can track it separately later.

bjornharrtell avatar Nov 12 '22 13:11 bjornharrtell

I note an issue with generate_code.py. It tried to use prefix to handle the tests/ts subfolder but that is no good, it changes the relative paths for the generated code. I tried to fix that by using the cwd param for flatc() function but it doesn't work it doesn't find the schema.

bjornharrtell avatar Nov 12 '22 14:11 bjornharrtell

Fixed generate_code.py.

bjornharrtell avatar Nov 14 '22 17:11 bjornharrtell

buildkite issue looks like something is assuming typescript bundle output which as mentioned is no longer a thing. Bundled output is javascript only. Unable to quickly figure out where that assumption is...

bjornharrtell avatar Nov 14 '22 17:11 bjornharrtell

I've attempted to fix bazel but I'm stuck with that it needs esbuild installed to generate the bundled code but I can't add it in presubmit.yml because it doesn't seem node is available at all. I'm not sure where to else try and add it. Any ideas @dbaileychess ?

bjornharrtell avatar Nov 14 '22 17:11 bjornharrtell

Looks like it needs to be plugged in with https://www.npmjs.com/package/@bazel/esbuild... Looks like bazel has it's own entire ecosystem and wrapped tools.

bjornharrtell avatar Nov 14 '22 17:11 bjornharrtell

@jkuszmaul Could you help with this PR? The buildkite CI is not working since the esbuild binary isn't present for bazel. I tried adding the dependency, but I have never use bazel/yarn/or really npm, so I am not sure what needs to be done.

dbaileychess avatar Dec 01 '22 03:12 dbaileychess

@dbaileychess I can help make what you want to build build; it should be reasonably straightforwards for me to just do. It sounds like you are still deciding exactly where to call esbuild, so I don't want to make it work with the solution you aren't going to use.

jkuszmaul avatar Dec 01 '22 04:12 jkuszmaul

@jkuszmaul Thanks for your fast reply and willingness!

Do you have thoughts on how such a bundler should be called? I know you did a lot with --ts-flat-files that this is replacing, so you might have some opinions in this space.

dbaileychess avatar Dec 01 '22 04:12 dbaileychess

@jkuszmaul I've just now reworked so that it will not run esbuild automatically (but it will output it as a hint). I will try to get it into the testing scripts instead and actually verify the output. Let me know if you have any questions on this PR in general as we try to make it mergable.

bjornharrtell avatar Dec 10 '22 10:12 bjornharrtell

I've now finished implementation so that bundled commonjs modules are output by TypeScriptTest.py and tests from flatbuffers 1.x (JavaScriptTestv1.cjs) are run against /monster_test_generated.cjs to verify both that the bundle works and is 1.x compatible.

bjornharrtell avatar Dec 10 '22 11:12 bjornharrtell

@jkuszmaul for some reason I can't reply on your comment above about the print out. Indeed the print out is the only thing that --ts-flat-files does now and I agree it would be good to document somewhere. But it's not trivial to do meaningful documentation on the subject.

bjornharrtell avatar Dec 13 '22 08:12 bjornharrtell

Oh, doesn't this mean we lose type information when building to a single file? Is there a way for esbuild to generate a declaration file or something so that we can still rely on type information? Or am I missing something?

jkuszmaul avatar Dec 18 '22 01:12 jkuszmaul

https://github.com/jkuszmaul/flatbuffers/tree/ns-entry-points gets most of the way there on the bazel rules, but I stopped when I ran into the wrong path getting printed from flatc, and since some discussions are still outstanding.

jkuszmaul avatar Dec 18 '22 01:12 jkuszmaul

Oh, doesn't this mean we lose type information when building to a single file? Is there a way for esbuild to generate a declaration file or something so that we can still rely on type information? Or am I missing something?

Not sure what you mean exactly. When consuming from typescript you would typically use modules directly not this single file bundle. The single file bundle is legacy JavaScript compatibility in my view.

You can get a source map though with esbuild with flag --sourcemap perhaps that is what you are after here?

bjornharrtell avatar Dec 18 '22 10:12 bjornharrtell

Not sure what you mean exactly. When consuming from typescript you would typically use modules directly not this single file bundle. The single file bundle is legacy JavaScript compatibility in my view.

You can get a source map though with esbuild with flag --sourcemap perhaps that is what you are after here?

The goal of the --ts-flat-files flag is specifically to produce a single typescript file. The reason for this is that Bazel really wants to statically know exactly what files are going to be output by a build step prior to reading the input file, but we still want the benefits of typescript. A similar objective would be achieved by producing a .d.ts file and implementing .js/.cjs file.

jkuszmaul avatar Dec 19 '22 03:12 jkuszmaul

Not sure what you mean exactly. When consuming from typescript you would typically use modules directly not this single file bundle. The single file bundle is legacy JavaScript compatibility in my view. You can get a source map though with esbuild with flag --sourcemap perhaps that is what you are after here?

The goal of the --ts-flat-files flag is specifically to produce a single typescript file. The reason for this is that Bazel really wants to statically know exactly what files are going to be output by a build step prior to reading the input file, but we still want the benefits of typescript. A similar objective would be achieved by producing a .d.ts file and implementing .js/.cjs file.

This PR makes it so that the modular output can be used to bundle JavaScript in a standard way. I'm not aware of any standard tools for TypeScript bundling. I know that is contrary to what the flag says and I propose above that the name should be changed. In my understanding --ts-flat-files was a hack to be able to produce a single JavaScript bundle to resemble what you got with flatbuffers 1.0, not a goal itself.

There seems to be some work towards creating bundles type definitions using esbuild, rollup and other tools but I see nothing official at his point. But the whole thing of Bazel driving this need feels wrong to me. :(

bjornharrtell avatar Dec 19 '22 11:12 bjornharrtell

There seems to be some work towards creating bundles type definitions using esbuild, rollup and other tools but I see nothing official at his point. But the whole thing of Bazel driving this need feels wrong to me. :(

It definitely feels awkward, but it's a design decision that Bazel made, and my general experience has been that it's not typically too hard to accommodate. The main alternative in this case is to require that users list out all the files that will be generated for each fbs file (or have them list every type and then generate the list of files to expect). But that sucks for users, hence why I preferred to just concatenate the files in flatc.

jkuszmaul avatar Dec 19 '22 15:12 jkuszmaul

There seems to be some work towards creating bundles type definitions using esbuild, rollup and other tools but I see nothing official at his point. But the whole thing of Bazel driving this need feels wrong to me. :(

It definitely feels awkward, but it's a design decision that Bazel made, and my general experience has been that it's not typically too hard to accommodate. The main alternative in this case is to require that users list out all the files that will be generated for each fbs file (or have them list every type and then generate the list of files to expect). But that sucks for users, hence why I preferred to just concatenate the files in flatc.

I would get sad to see this PR go stale so I'm willing to try and see if it's possible to generate the type declaration bundle of you think that it a way forward.

bjornharrtell avatar Dec 19 '22 16:12 bjornharrtell

There seems to be some work towards creating bundles type definitions using esbuild, rollup and other tools but I see nothing official at his point. But the whole thing of Bazel driving this need feels wrong to me. :(

It definitely feels awkward, but it's a design decision that Bazel made, and my general experience has been that it's not typically too hard to accommodate. The main alternative in this case is to require that users list out all the files that will be generated for each fbs file (or have them list every type and then generate the list of files to expect). But that sucks for users, hence why I preferred to just concatenate the files in flatc.

I would get sad to see this PR go stale so I'm willing to try and see if it's possible to generate the type declaration bundle of you think that it a way forward.

This PR definitely feels like the right direction to be going for generating single files (i.e., relying on auto-bundlers instead of trying to manually concatenate files and adopt the maintenance burden of keeping that correct). If there really are no good options for generating the type declarations, I think I'd be willing to take your current approach, but it would definitely feel like an unfortunate step backwards.

jkuszmaul avatar Dec 19 '22 16:12 jkuszmaul

@jkuszmaul I've spent some time trying out different experimental ways to bundle type declarations without success. The closed issue at https://github.com/Microsoft/TypeScript/issues/4433 is also telling that the interest to provide the ability to bundle type declaration is low. In my opinion if you want the benefits of TypeScript you really need to use the modular output as is.

I've added generation of the modular type declaration to tests/ts and also added the generated output to this PR.

@jkuszmaul and @dbaileychess I hope this can progress regardless. Let me know if there is something else I can do.

bjornharrtell avatar Dec 23 '22 15:12 bjornharrtell

Unfortunately I find it hard to resolve the merge with master now due to https://github.com/google/flatbuffers/pull/7748. Will make a new attempt when I have some more time.

bjornharrtell avatar Jan 11 '23 22:01 bjornharrtell

I've been able to do the merge now as proper as I can but tests specific to no_import_ext fail like follows:

no_import_ext/optional-scalars.ts:3:30 - error TS2835: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './optional-scalars/optional-byte.js'?

This is expected to me and I don't quite understand how the tests passed in #7748. @ink-su and @sunwen18 do you have any ideas?

bjornharrtell avatar Jan 12 '23 12:01 bjornharrtell

Nevermind I think I understand it now, it needs to be tested using the "old" node resolution mode of typescript. I've separated the tests.

But I see another issue now. When using prefix (-o) it looks like module paths become wrong for no_import_ext but strangely enough not for union_vector... sigh.

bjornharrtell avatar Jan 12 '23 13:01 bjornharrtell