olov / ng-annotate

Add, remove and rebuild AngularJS dependency injection annotations

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

The future of ng-annotate

olov opened this issue · comments

I have maintained ng-annotate for almost three years now and I think it's now time for me to either pass the maintainer bit forward or to declare ng-annotate feature complete, disabling implicit matching.

I created ng-annotate because it seemed tedious and error-prone to keep two parameter-lists in sync. At the time most angular-applications had similar enough structure that detecting functions to annotate automagically seemed like a good idea. But as angular progressed and its community grew, the slope became more and more slippery. I eventually added support for explicitly marking up which functions to annotate (first via /*@ngInject*/, then via "ngInject") but regrettably to late. The automagic expectation still remains, as reflected in the issue tracker. Please note that "ngInject" works great in practice - it takes a couple of seconds to type it in, you don't need to keep two parameter lists in sync and it's crystal clear which parts of your code will be transformed and which won't.

Which brings us to TypeScript and ES2015+ support. With "ngInject" markup it works fine as long as ng-annotate is fed with the tsc/babel/.. processed output. With implicit matching it's a whole different beast on top of the already slippery automagic-slope. I've consistently said no to adding ES2015+ support for this reason, and there are lingering pull requests and issues (mostly feature requests).

I see two paths forward (are there other options?):

  1. I will ship a vastly simplified ng-annotate 2.0, supporting explicit "ngInject" matching only. It will be (mostly) feature frozen but bugs will be fixed. An unimportant but still nice side effect would be that ng-annotate becomes even faster.
  2. I pass it along. The new maintainer(s) can revisit my decision to not support unprocessed ES2015+ input and how far to further slip down the implicit slope.

Thoughts on this? I'm interested in hearing from users, current and future contributors and from anyone interested in taking on the maintainer role.

@olov First of all thanks for keeping us up to date

I like path 1

I like the first option - very reliable, less surprises.

All that'd need to happen going forward is to support new ECMAScript features where needed - for now injecting arrow functions & ES6 classes.

@olov Thanks for the feedback. I would go for the 1st option.

Besides, it doesn't prevent anyone to fork this repo and support ES6.

@olov I agree with the others, option 1 is the way to go. as Jeremy says, there's nothing stopping anyone from going forward anyway.

On the point of the slippery slope: I've found myself in situations where I wasn't sure if I could leave out the explicit annotation, and as with other instances of erring on the sides of consistency and clarity, I was on the path to annotating everything anyway. After all, even if I knew exactly when an un-annotated function would be injected and when not, why keep those guessing who come after me? Just my two cents 👍

Oh, and thank you for ng-annotate 😄

Go for option 1! As long as #220 is included

Option 1. Unprocessed ES6 shouldn't be supported in my opinion. Use toolchains, dudes.

Unprocessed ES6 shouldn't be supported in my opinion. Use toolchains, dudes.

Why? There are lots of ES6-compatible environments now and not every project has to support IE. Also, it's perfectly fine to want to run your code in ES6 mode during development for easier debugging and having ng-annotate work there would help a lot.

Requiring transpiling ES6 to ES5 in all cases decreases usefulness of ES6 a lot.

If we go with option 1, it would be possible and probably quite easy to implement ng-annotate functionality not as a standalone tool, but as a babel 6 plugin. That way it would stay compatible with any toolchain and would support ES6 out of the box.

I would go for 2.
It seems you don't have time even to merge valid PRs, and besides, maintainers could also help towards the "simple" options.
It's not an exclusive path.

I've been maintaining a fork of ng-annotate that reimagines this project as a babel plugin instead of a standalone tool.

Currently, the plugin passes ng-annotate's test suite, supports ES6 sources (and annotating things like class constructors), and works with implicit annotations.

Additionally, as a Babel plugin, we get to take advantage of Babel's fairly robust parser (with support for modern language features) and suite of transformation tools. Developers who are already using Babel should also see significant performance improvements over ng-annotate, as the sources only need to be parsed once. (Even with implicit matching, the plugin does not appear to add much overhead beyond the work already being done by the parser.)

Regardless of ng-annotate's future, I intend to maintain my fork. If we feel that it's OK for ng-annotate to live on as a Babel plugin, we could treat my fork as a successor to this project.

(PS. Huge thanks to @olov for all of the work he's done over the years on ng-annotate!)

@schmod Very interesting! Does it support options? The ideal setup for me is to:

  1. Always use 'ngInject';
  2. Disable any possible heuristic that the plugin has and rely only on the 'ngInject'; pragma.
  3. Have support for ES6 classes & arrow functions in the following way:
class Circle {
    constructor(service1, service2) {
        'ngInject';
        /* code */
    }
    method(param1, param2) {
        /* code */
    }
}

angular
    .module('myModule')
    .component('myCircle', {
        template: '...',
        controller: CircleController,
    })
    .run((service1, service2) => {
        'ngInject';
        /* code */
    });

Is that possible to achieve with your plugin?

  1. Right now, there isn't a configuration option to disable implicit matches, but it should be fairly straightforward to implement. I'll put that on the roadmap.

2, 3. I just pushed a new release that supports arrow functions and prologue annotations in class constructors.

Actually, explicit-only matching turned out to be really easy to implement, and is now released in v0.4.0 of babel-plugin-angularjs-annotate.

@schmod that sounds awesome!

I haven't yet tried babel-plugin-angularjs-annotate, but based on reading the README these are the changes that I'd make:

  1. Delete implicit matching stuff. If not, make it opt-in rather than opt-out. Deleting would be better, for a bunch of reasons.
  2. As a consequence of 1, delete all implicit matching stuff in the README (or move to a separate document).
  3. Guide users to "ngInject" whenever possible because comments are brittle. var x = /* @ngInject */ ($scope) => { ... }; becomes var x = ($scope) => { "ngInject"; .. };. It should be easy to let babel-plugin-angularjs-annotate strip away the prologue string to prepare for optimal minification.

Just my opinions but I think it would make life easier for developers and users alike. :-)

I avoided copying the entirety of ng-annotate's README for reasons that I no longer remember.

If the Babel plugin is indeed the future of ng-annotate, I should indeed aim to provide comprehensive documentation, rather than directing new users to ng-annotate's README.

Some other thoughts:

  1. Why is implicit matching so problematic? Are there major known issues? I understand that it's not reliable in 100% of situations, but it's generally been pretty solid for me.
  2. Ditto for comments. Are there specific transformations that are known to mangle comments, but leave prologue directives intact?
  3. With the above things in mind, I think that it might be a good idea to have a "strict" mode that looks for implicit matches, and prints a warning (or error) if they haven't been explicitly marked for annotation. [Yes, this functionality probably belongs in a linter.]

Another possibility: A special mode that does implicit matches, and then adds prologue directives to those functions (instead of adding the actual $inject annotations), while stripping out existing comment-based /* @ngInject */ hints and explicit $inject annotations.

This could be useful for preparing existing code to use our recommended "best practices."

@schmod I've just switched one of my projects to your plugin and it worked like a charm in my ES6-based code. I no longer have to disable strictDi in the development ES6 server mode. Thanks!

@schmod

Why is implicit matching so problematic? Are there major known issues? I understand that it's not reliable in 100% of situations, but it's generally been pretty solid for me.

From a user's point of view, I consider it a best practice to have every occurrence explicit. The main reasons being that this

  1. removes the author's need to distinguish cases where implicit matching works versus those where it doesn't
  2. helps the reader's understanding of what's happening

Of course, there will be authors like yourself, as well as myself at an earlier time, who will like the implicit matching and have no issues. However, since you will need to use explicit annotations anyway as soon as implicit matching fails you and implicit matching greatly increases the code's complexity, I think the cost probably outweighs the benefits.

I've been stuck on 1.2.28 for so long that I keep forgetting that strictDi exists.

strictDi mitigates most of the risks of explicit-only annotations (ie. that you've forgotten an "ngInject" somewhere and won't discover it until you test a minified build).

I think it's reasonable for us to recommend explicit annotations and strictDi for new users.

  1. Implicit matching is problematic because a) it's a never-ending story and b) false positives (breaking code). see the ng-annotate issue tracker for lots of more info. :)
  2. Comments are problematic because of rewriting tools shuffling them around (see issue tracker here too). That's why I came up with "ngInject" in the first place (and yes most proper source rewriters respect prologues).
  3. I have no problem with a strict mode. :)

My comments are just meant as advice based on my experience of maintaining ng-annotate, so don't feel any pressure of doing this or that to please me.

I think your Babel plugin is indeed the future of ng-annotate. @mgol since we had discussions about a Babel plugin not long ago, I'd like to hear whether you'd be happy with this too?

As I've mentioned, I'm in favor of leaving implicit matching in place for the time being.

However, you've convinced me that 1.0.0 (ie. the first release I'll make with breaking changes) should turn of implicit matching by default, and the docs should clearly emphasize explicit annotations and prologue directives as the preferred way of doing things.

I like this because it makes the documentation really straightforward:


Usage

tl;dr;

Add 'ngInject'; to the top of any function body that will be invoked with Angular's DI injector. That's it.

angular.module('myMod').service('mySvc', function($scope){
   'ngInject';
   $scope.foo = "bar";
});

angular.module('myMod').directive('myDir', (urlBase) => {
   'ngInject';
   return {
      restrict: 'E',
      controller: ($http) => {
          'ngInject';
          $http.get(urlBase + '/foo');
      }
   };
});

There's also another babel plugin that seems like it's being developed in parallel:
https://github.com/marcioj/babel-plugin-angular-annotate

@mgol since we had discussions about a Babel plugin not long ago, I'd like to hear whether you'd be happy with this too?

Yup, I'm happy with the plugin!

There's also another babel plugin that seems like it's being developed in parallel:
https://github.com/marcioj/babel-plugin-angular-annotate

It seems it was created from scratch, though. And a large part of it is supporting specific "configurations" which are string representations of object property access paths which should have parameters annotated (like "$httpProvider.interceptors.push"). It also seems it doesn't support any explicit annotation style. So I definitely prefer your plugin.

Alright, I think the timing is right to get this done now. @schmod what's the current status and how would you prefer to proceed with the migration? I'm thinking a prominent message at the top of the ng-annotate README that briefly explains that ng-annotate is now deprecated in favor of babel-plugin-angularjs-annotate, preferably with a link to a .md file on your repo that explains the easiest way to migrate (for existing ng-annotate users). The babel-plugin-angularjs-annotate README should also be updated to make it clear that it's the ng-annotate successor. If it's possible to disallow new issues on the ng-annotate github page then that will happen too. Anything else we need to do?

I think the steps forward are:

  1. Bump babel-plugin-angularjs-annotate to 1.0.0 and turn implicit matching off by default
  2. Write a migration guide, and update README.md to reflect that we're considering the new project to be "stable" -- I'd like to PR this and ask a few of you to look it over before I merge it.
  3. Update ng-annotate's README.md to point new users to the Babel plugin.

I might have some time for this in the coming week. Apologies that I've been busy lately.

I've taken a stab at updating the documentation for babel-plugin-angularjs-annotate in preparation for the 1.0.0 release.

https://github.com/schmod/babel-plugin-angularjs-annotate/tree/next

Proofreading and improvements are welcome ;-)

@schmod great - let me know when you're ready and I'll update the ng-annotate README with a deprecation notice and a link to your plugin. I'll also push a (final) release of ng-annotate to npm so that the updated README is there as well.

Thanks to much for the great work, @olov and @schmod! This is a prime example of how open-source software should happen. I'll gladly migrate my Angular projects to use the new babel plugin now and will let you know (in the new repo) if I run into any problems.
(Sorry if this is considered spam for some people who get mail notifications for this thread but I just wanted to express my gratitude.)

One thing to consider: in the typical Angular 2+ setup used via Angular CLI if you want to mix in Angular 1 components, you won't be able to use ng-annotate as the Babel plugin doesn't understand TypeScript. But the current ng-annotate doesn't understand TypeScript as well so this is not making matters worse.

In that scenario, you'd probably want to opt for the TypeScript compiler to output ES6, and then run the output through babel to transpile to ES5. (Or, alternatively, there's nothing stopping you from running babel against ES5 sources)

@olov @schmod Can we proceed with the ng-annotate deprecation? I think the Babel plugin works really well right now and most people don't know ng-annotate is effectively deprecated and that they should move to the Babel plugin.

It is done. Thanks everyone! I'm leaving this issue and #253 open for historical reasons.

@olov Can you run:

npm deprecate ng-annotate "My deprecation message"

so that people installing the package see the warning?

I'll try to find time this week to update my repo to reflect ng-annotate's deprecation (and finally get 1.0 out the door)

I really like and respect this community of commenters and in particular @schmod. I'm a newbie developer and was taught ES5 and now trying to transition into more functional programming in Javascript using ES6. I'm still have some minor difficulties wrapping my head around it. What @schmod has created is really great and I have been highly anticipating v1.0. Any word yet on a timeline? I would also like to contribute to any open source project based upon my beginner skill set. Any recommendations? I don't care how mundane or minor my role is. I just want to be more active in the community and grow and learn.

Here's a fork that has been patched to work with imports/exports in ES6: https://github.com/bluetech/ng-annotate-patched

There is another fork from nevcos that supports ngInject for ES6 classes. It's little behind bluetech fork but useful in own way.

Also, if you use ng-annotate with gulp, take a look at gulp-ng-annotate-patched

Actually, I've improved nevcos fork a little and everything got merged to ng-annotate-patched - feel free to use it with working support for ES6 classes.
BTW shouldn't ng-annotate-patched be mentioned in this library README file (and maybe npm deprecation message), so others can find it more easily?