Usage of `ts_*` causes a load-time resolution of `@npm` for non-FE target analysis
🚀 feature request
Relevant Rules
ts_config
And possibly anything else loaded out of @bazel/typescript
Description
Using a rule such as `ts_config` shouldn't require a load of the `@npm` external repository, which is a heavy load for all packages and cannot be done piecemeal (AFAIK).Because of this, any BUILD.bazel file loading ts_* requires a @npm load-time resolution even if the target in question does not require @npm. For example, we have a base tsconfig.json and have generated ts_config rules for all of them, however that root BUILD.bazel also exports a config file which is used by some Go binaries, and as such we have Go builds which require loading @npm as a whole.
Describe the solution you'd like
Specifically for `ts_config` (or any other rule which doesn't require `@npm`), it seems inappropriate to load it via an `npm` package. Meanwhile, though I think it would be doable for other rules to be loaded straight from `rules_nodejs` as well, `ts_library`/`ts_project` might not make a big impact as I believe their hardcoded dep on `tsc` would keep the load-time resolution of `@npm`?Describe alternatives you've considered
For now we're adjusting our auto-generation as we shouldn't actually need the base `tsconfig.json` to be available via Bazel (but rather, it exists more for IDE integration). However, it still felt like this might deserve some discussion and thus thought I should file this issue.
Yup, load statements in any BUILD file read during the analysis phase do cause eager fetches, and so that's a reason to organize code carefully avoiding a mix of load statements in a single package.
For typescript in particular, this is a conscious design choice. The ts_* rules depend on the typescript npm package, so we distribute them as an npm package. That allows .js files in the @bazel/typescript package to require('typescript') and resolve to the correct version of typescript that you have installed, and ensures that Bazel uses the same TypeScript version as your other tools like the editor.
You don't have to fetch the @bazel/typescript package in the same npm_install/yarn_install as all the other packages though. I think it's a good practice to have a separate package.json for the tools that are run during the build, as opposed to the runtime dependencies. Then you use a separate npm_install(name = "npm_tools") or something, and then your load statements cause an eager fetch of a smaller set of packages.
I've also been wanting to work on a different approach here, where the typescript rules could be distributed as a starlark module instead. If we did that, we'll hit the problem above that any JS code in that package can't require from user-installed packages. So at minimum we'd have to provide some test target you could use to assert that Bazel is using the same TS version as your editor and other tools. Probably not that hard. Then this starlark module would be completely hermetic, fetching the typescript npm package itself, not even using an npm install since there's no postinstall step required. That would look like my recent https://github.com/aspect-build/rules_swc which does it that way for swc (no @npm or npm install is needed there to use the SWC transpiler even though their CLI is a nodejs program).
That... sounds amazing. Why would I need JS code in the typescript rules package to reference user-installed packages? Self-contained, in-build chains sounds way better. I'm used to the Go world where Bazel uses a well defined, in-build version of Go, and we we just make sure our external tools and IDEs use the same version.
Your SWC stuff looks hot. I would love for typescript rules to work the same way.
@dragonsinth thanks for the enthusiasm. I'm on the fence about spending time on it right now...
For TS, the only place we require from userland is the @bazel/worker package and we can obviously grab that hermetically as well. Other packages like cypress, babel, webpack, etc are a lot trickier since the user brings plugins that they've installed in their node_modules tree and the rule JS files need to reference somehow. Or you'd have to be really disruptive and make users move their plugin definitions into the bazel toolchain.
okay I had an insight about how easy we can already make this https://github.com/bazelbuild/rules_nodejs/pull/3204
I think it's close to landable, just need to eliminate all the code duplication by having packages/typescript vendor in these files and load from them.
#3204 looks awesome so far
taking another shot at this, https://github.com/aspect-build/rules_js/compare/ts_no_npm as a demo
With https://github.com/aspect-build/rules_js/pull/27 landed, there's at least an existence proof that this can be done. Using aspect_rules_js is one option. I think it's also feasible to pull that approach into this repo, though it would have to live in build_bazel_rules_nodejs package rather than the new core rules_nodejs because the latter doesn't have a nodejs_binary yet.
This issue has been automatically marked as stale because it has not had any activity for 6 months. It will be closed if no further activity occurs in 30 days. Collaborators can add a "cleanup" or "need: discussion" label to keep it open indefinitely. Thanks for your contributions to rules_nodejs!
Fixed in https://github.com/aspect-build/rules_ts