-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Research Closure Compiler + Typescript Support #134
Comments
Yay for Closure Compiler 👍 How do you see developers using MDC-Web with their applications build steps? Are they to just include a pre-compiled distributable version which they should not run through Closure Compiler as part of their application build step, in which case won't MDC-Web need to provide an externs file? or will users be able to included the MDC-Web source and have it be compiled (using |
The most probable way is through webpack/gulp/grunt via google-closure-compiler-js. The trickiest part is module resolution. While all of our code will be compilable via closure, there's still the matter of resolving ES2015 import statements. Because closure has no notion of node's module resolution mechanics, the code will have to be pre-processed in such a way that its dependencies are resolved. I'm currently looking into how we're going to do this in order to test that our code is compilable via closure, as well as to produce the correct Typescript typings via clutz if that's possible.
As alluded to above, yes, as long as they make closure aware of how to resolve module dependencies within MDC-Web. |
Great - thanks for the info 👍 |
Looks like Closure Compiler is working on adding node module resolution capability, which will make some of the work we have to do around getting modules to get recognized much easier. google/closure-compiler#2130 |
Filed google/closure-compiler#2257 regarding using identifiers from re-exported modules as types. This would create issues for us since we re-export all of our foundation classes through |
Research Findings
This comment documents my findings on researching how to properly provide closure + TS support. All of my work can be found on the experimental/closure-research branch. ApproachSo for this, I decided to go with the Closure-annotated source files + Clutz approach outlined in the original issue. There were a few reasons for this:
To prove the viability of this option, I attempted to compile our icon-toggle package via closure, and then generate type definitions for it via clutz. Icon-toggle is a feature-rich component that depends on the mdc-base and mdc-ripple packages, and has a non-trivial adapter API. I felt it would be a solid litmus test for the viability of this approach. Implementation NotesSetting up closure - ✅Closure provides two packages via npm - google-closure-compiler, which is a standalone jar of the Java compiler, as well as google-closure-compiler-js, which is a GWT-transpiled version of closure compiler. For this experiment, I chose to go with
Which deterred me from considering it as highly. But the real reason is that internally, teams will be building our code using the Java version of the closure compiler, and I wanted this to be as close to the real-world scenario for using closure as possible. The drawback of this approach is that it requires Java. However, most modern computers ship with Java, and installation on TravisCI seems easy enough. Furthermore, I chose not to use closure with webpack. Again, this mostly has to do with how closure is used within Google. Unlike with Webpack, where closure seems to be used solely to minify and optimize scripts, in Google the closure compiler both transpiles ES2015 sources as well as optimizes it. Again, in an effort to be as aligned with the predominant user of closure as possible, I wanted to simulate what it would be like if it was being built by bazel. This means that our CI system could eventually incorporate these closure build tests for all of our packages, giving us a type checking system and ensuring that PRs never cause our closure builds to fail. So to install it I simply ran:
Which means closure is called by executing Potential ProblemsNone! Setting up Clutz - ❌Setting up Clutz was not easy, and required manual effort. First, since there is no binary distribution, it has to be built from source. This requires gradle to be installed on the host OS, which I did via homebrew.
Then I had to build the library and ensure that the built binary is on your path. Building clutz also requires the user to install clang-format in the same directory as clutz via npm, or else it won't build.
Potential ProblemsSetting up clutz on a machine is a very manual process at this point. This means we would either have to write scripts to bootstrap clutz for contributors, or thoroughly document how to set up clutz. Both have major drawbacks with regards to maintaining the procedure for setting up Clutz in case the way it's built or distributed changes. Another solution could be to work with the Clutz team to begin providing binary distros. Compilation via Closure - 🔜 ✅Since the goal of this experiment was to test the viability of compiling mdc-icon-toggle via closure, I hacked together a script to do so. The script essentially creates a
Making icon-toggle compilableIcon-toggle was a great example component because of its reliance on both MDC-Base and MDC-Ripple. Because it relied on these components, it meant that they also had to be closure-compilable as well. Here's what I came up with:
/** @record */
export default class MDCComponentAdapter {
/**
* @param {string} className
*/
addClass(className) {}
/**
* @return {string}
*/
getAttr() {}
// ...
} These
Thus, the /** @template T */
export default class MDCFoundation {
// ...
/**
* @param {T=} adapter
*/
constructor(adapter = {}) {
/** @protected {!T} */
this.adapter_ = /** @type {!T} */ (adapter);
}
// ...
} And /**
* @template T
*/
export default class MDCComponent {
// ...
/**
* @param {!Element} root
* @param {T=} foundation
* @param {...?} args
*/
constructor(root, foundation = this.getDefaultFoundation(), ...args) {
/** @protected @const {!Element} */
this.root_ = root;
this.initialize(...args);
/** @protected @const {T} */
this.foundation_ = foundation;
// ...
}
// ...
} Initial problems with compilationExport aliasingSee google/closure-compiler#2257. This is an easy enough problem to work around, considering that we can simply import files from exactly where they are exported, rather than through their aliases. Also note that we can still export aliases for our external users, we just can't use them internally. This limitation for ourselves must be documented for our contributors. NodeJS import system (fixed)See google/closure-compiler#2130. Before this PR, closure could not import any of our code without us re-writing the import statements, similar to how we are doing it in this prototype. With this PR merged, this will no longer be an issue since closure can now use Node's module resolution algorithm to resolve modules 🎉 Type-checking issues in getters/settersSee google/closure-compiler#2261. Because we make heavy use of getters and setters within our vanilla components, this means that we need to disable Strange-looking code for those who have never seen closureIf you've never annotated JS for closure before, things like this: /**
* @typedef {{
* isActivated: boolean,
* wasActivatedByPointer: boolean,
* wasElementMadeActive: boolean,
* activationStartTime: number,
* activationEvent: ?Event
* }}
*/
let ActivationState; // eslint-disable-line no-unused-vars Probably look extremely strange. We should definitely maintain some sort of documentation that helps onboard our contributors to the idiosynchrasies of closure, and perhaps provide a "crash-course" of sorts regarding what they need to know about the compiler in order to productively contribute to our code-base. Basically, we want to make it easy as possible for them to contribute without having closure knowledge gaps get in the way. Potential ProblemsClosure's New Type Inference (NTI) systemClosure is working on a "new type inference" system which introducer stricter checks and hardened static analysis from the compiler. While this will prove useful, the way in which we structure our code currently does not adhere to it. For example, attempting to de-reference a parameter of a template type From speaking with the team, it seems that NTI is still being actively developed, and although there are a few apps using it, not very many are. To that end, they thought it would be fine if for now our code was compilable using the old type system. Dead code from
|
The linked milestone in the last comment has disappeared. Is there a new public location that's tracking the status of the Closure/TypeScript project? |
hey @traviskaufman - i know it has been ages but what's the status on this? since that milestone is now gone, is MDC buildable via the Closure toolchain? |
yoooooo @traviskaufman lol |
this is such a sad issue thread tbh |
By the time MDC-Web reaches beta, we need to provide support for typed ECMA variants. Specifically, we need support for Closure Compiler (Closure) and Typescript (TS). This is the issue where all research around our strategy for supporting Closure + TS should be done.
Closure Compiler Support
Google Closure Compiler support is required in order to support the Google projects and properties which are built around this toolchain. Concretely, MDC-Web must be able to compile with ADVANCED_OPTIMIZATIONS enabled, and produce no errors or warnings. There are implications for internal support as well, but that is outside the scope of this issue.
Verification Plan
npm run test:closure
which runs closure on all packages, but does not output anything. This can be done using closure's--checks_only
flag. The advantage here is that nothing about our build system changes.Typescript support
Typescript is angular's de facto language choice. It also seems to be the most popular typed ECMAScript variant. For these reason, I believe it's important to provide first-class support for Typescript users wanting to use MDC-Web. Concretely, MDC-Web should provide type declaration files for all components, foundations, and adapters.
The type declaration files can live in the component package directory, and should be called
index.d.ts
This will allow typescript's module resolution algorithm to automatically discover these typings. Furthermore, having type declarations should become a requirement for all components moving forward. The TS handbook as a module declaration file template that we could use as a starting point for our TS modules.Verification Plan
I'm not sure how to validate type declaration files alone, so more research has to be done on this.
Implementation Options
There are a few implementation options which I will discuss below. Any additional implementation options surfaced by the community should be added to this section. I personally think all of these options should be experimented with, but we could rule some out via discussion on this issue.
Closure-annotated source files + manual typings
All source files are annotated using JSDoc for the closure compiler.
Type declaration files are manually authored in addition to adding closure compiler annotations.
A page within
docs/
could be created outlining any non-standard practices within our source code that are closure-specific (e.g. using expressions for@typedef
s, using dummy classes for@record
types, etc.)Pros
Cons
Closure-annotated source files + Clutz
All source files are annotated using JSDoc for the closure compiler.
Angular's Clutz tool is used to emit type declaration files from closure-annotated source files. This can be added as part of a pre-commit hook.
Pros
Cons
Typescript Source files + Tsickle
Instead of writing source files in ES2015, source files are written using Typescript.
Before publishing to npm, and extra build step will be added to transpile them to their ES2015 sources using CommonJS as its module system. This would ensure the built source files would work with module loading systems such as webpack. Alternatively, we could preserve the ES2015 imports.
Angular's Tsickle could be used to annotate the source files for closure.
The TS compiler could output proper declaration files for our project, which could be added to version control since the declarations files would reflect the public API.
Pros
Cons
@material/ripple
before deeming it viable).The text was updated successfully, but these errors were encountered: