Skip to content

BUILD: revamp frontend building #2003

@stephanwlee

Description

@stephanwlee

Current state

Most of our build is done in our Vulcanization process today.
Before bringing up what a Vulcanization entails, it is important to note that dependencies of a module is defined in HTML via script and import tags. Vulcanization talks the dependency graph and:

  • first builds TypeScript files (along with necessary d.ts files) that outputs .js and a .d.ts file
  • compile all JSs (generated included) using JSCompiler into one bundle
  • depending on options or "environment", it inlines the bundle into the script tag
    In all of these processes, it makes sure a dependency is properly provided via Bazel deps.

One interesting artifact of Vulcanization is the concept of webPath. Instead of using relative or absolute path to a module, modules, in its tf_web_library, define a webPath which indicates a path of the module in the bundle artifact. For instance, say there is a module in a/b/c/d/foo.js which defines webPath of /tf-foo/foo. When other modules use the foo, it will define dependency as <import href="/tf-foo/foo" /> (or a relative path from its webPath). This loosely is related to concept of webfiles of Bazel/rules_closure, though, stephanwlee lacks profound knowledge in it yet.

The design choice of webPath and Vulcanization impact how we author JS/TS. The natural way of defining dependency in JavaScript is via CommonJS or ES module (modern browsers have native ES module support) but we have to use namespace and window object (e.g., window.tf.graph.common) because we define dependency via HTML. This is not to say concept of Vulcanization is incompatible with es module completely but it requires non-trivial changes.

Future state

In the future, we want our solution to:

  • use Bazel: we don't want part of our app to be on Bazel and others on another build system
  • have ability to easily test FE (karma integration at the very least is required)
  • have less customization of build system if possible (i.e., deprecate Vulcanization.java)
  • other framework (e.g., React) friendly

With above requirements in mind, this section only is attempting capture one point in the vast solution space.

  1. Use Bazel/rules_typescript to build TypeScript
    This is the canonical way of building TypeScript with Bazel.

  2. Utilize Bazel/rules_nodejs as much as possible
    rules_nodejs allows us to pull dependencies from NPM and use node ecosystem to build and test.

  3. Move off of JSCompiler and use Webpack, Rollup, or None
    JSCompiler is quite sophisticated but it is a bit overkill for our application today. Shaving extra bytes off of JS payload is not worth the complexity and additional maintenance burden. Good alternatives to JSC are Webpack and Rollup.

We can also consider not bundling and loading compiled JavaScript using ES module. With HTTP/2 more mainstream and because our code is not written for Node.js, there is no reason to (1) build JS to shim node module system & Node APIs and (2) create one binary for performance optimization. This at least generally is very good approach for development. One drawback of this is that there are interesting third-party hosted TensorBoard and it may negatively impact them performance wise.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions