vite-ng-annotate
v0.1.0
Published
Vite plugin that adds AngularJS dependency injection annotations
Maintainers
Readme
⚡ vite-ng-annotate
Vite plugin that automatically adds AngularJS dependency injection annotations to your source code. Ensures your AngularJS app survives minification by converting function parameters into explicit string-based annotations. 🛡️
Inspired by babel-plugin-angularjs-annotate and ng-annotate.
📦 Install
npm install vite-ng-annotate --save-dev🚀 Usage
// vite.config.ts
import { defineConfig } from "vite";
import ngAnnotate from "vite-ng-annotate";
export default defineConfig({
plugins: [ngAnnotate()],
});🔒 Explicit-only mode
Only annotate functions marked with /* @ngInject */ or "ngInject":
export default defineConfig({
plugins: [ngAnnotate({ explicitOnly: true })],
});🔍 What it does
❌ Before (breaks when minified)
angular.module("MyApp")
.controller("MyCtrl", function ($scope, $timeout) {
// $scope and $timeout get renamed by minifier
});✅ After (minification-safe)
angular.module("MyApp")
.controller("MyCtrl", ["$scope", "$timeout", function ($scope, $timeout) {
// String annotations survive minification
}]);🧩 Supported patterns
🏗️ Module methods (implicit)
All standard AngularJS module methods are detected automatically:
// Inline array annotation is added to the function argument
myMod.controller("Ctrl", function ($scope, $timeout) {});
myMod.service("Svc", function ($http) {});
myMod.factory("Fact", function ($q, $http) {});
myMod.filter("myFilter", function ($sce) {});
myMod.directive("myDir", function ($compile) {});
myMod.animation("myAnim", function ($animate) {});
myMod.decorator("myDec", function ($delegate) {});
// .config() and .run() (no name argument)
myMod.config(function ($interpolateProvider) {});
myMod.run(function ($rootScope) {});🔧 Provider with $get
myMod.provider("foo", function (x) {
this.$get = function (a, b) {}; // annotated
// Also detects self.$get, that.$get, and return { $get: fn }
});
// Object-form provider
myMod.provider("foo", { $get: function ($scope) {} });🧱 Component config
myMod.component("cmp", {
controller: function (a) {}, // annotated
template: function (b) {}, // annotated
templateUrl: function (c) {}, // annotated
});📐 angular.module() config function
angular.module("MyMod", ["dep"], function ($interpolateProvider) {});➡️ Arrow functions
All patterns work with arrow functions:
myMod.controller("Ctrl", ($scope, $timeout) => {});
myMod.config(($interpolateProvider) => {});🔗 Reference following
Named functions and variables passed by reference get $inject added:
function MyCtrl($scope, $timeout) {}
angular.module("MyMod").controller("MyCtrl", MyCtrl);
// Produces: MyCtrl.$inject = ["$scope", "$timeout"];🏛️ ES6 classes
class MySvc {
constructor(dep1) {
this.dep1 = dep1;
}
}
angular.module("MyMod").service("MySvc", MySvc);
// Produces: MySvc.$inject = ["dep1"];🗺️ $routeProvider.when()
$routeProvider.when("path", {
controller: function ($scope) {}, // annotated
resolve: {
data: function ($http) {}, // annotated
},
dontAlterMe: function (arg) {}, // NOT annotated
});🧭 $stateProvider.state() (UI-Router)
$stateProvider.state("myState", {
controller: function ($scope) {}, // annotated
controllerProvider: function ($state) {}, // annotated
templateProvider: function ($timeout) {}, // annotated
onEnter: function ($state) {}, // annotated
onExit: function ($state) {}, // annotated
resolve: {
data: function ($http) {}, // annotated
},
views: {
main: {
controller: function ($scope) {}, // annotated (nested)
},
},
});💬 Modal and dialog services
$modal.open({
controller: function ($scope) {}, // annotated
resolve: { items: function (Svc) {} }, // annotated
});
// Also: $uibModal.open(), $mdDialog.show(), $mdToast.show(), $mdBottomSheet.show()💉 $injector.invoke()
$injector.invoke(function ($compile) {}); // annotated🌐 $httpProvider interceptors
$httpProvider.interceptors.push(function ($q) {}); // annotated
$httpProvider.responseInterceptors.push(function ($scope) {}); // annotated📋 $controllerProvider.register()
$controllerProvider.register("foo", function ($scope) {}); // annotated🏭 $provide methods
$provide.service("foo", function ($scope) {}); // annotated
$provide.factory("foo", function ($scope) {}); // annotated
$provide.decorator("foo", function ($scope) {}); // annotated
$provide.provider("foo", function ($scope) {
this.$get = function ($http) {}; // annotated
});⛓️ Chaining
All patterns work through method chains:
angular.module("MyMod")
.controller("A", function ($scope) {})
.factory("B", function ($http) {})
.config(function ($interpolateProvider) {});🏷️ Explicit annotation markers
💬 /* @ngInject */ comment
Place before a function, class, object, or property:
// Named function declaration -> $inject
/* @ngInject */
function MyCtrl($scope) {}
// Result: MyCtrl.$inject = ["$scope"];
// Variable assignment -> $inject
var x = /* @ngInject */ function ($scope) {};
// Result: x.$inject = ["$scope"];
// Object literal -> annotate all function values
var resolves = /* @ngInject */ {
foo: function (a) {},
bar: function (b, c) {},
};
// Single property
var obj = { /* @ngInject */ foo: function (a) {} };
// Class
/* @ngInject */
class Svc { constructor(dep1) {} }
// Result: Svc.$inject = ["dep1"];📜 "ngInject" directive prologue
Place as the first statement in a function body:
function Foo($scope) {
"ngInject";
}
// Result: Foo.$inject = ["$scope"];
// Works in class constructors too
class Svc {
constructor(dep1) {
"ngInject";
}
}
// Result: Svc.$inject = ["dep1"];🚫 /* @ngNoInject */ suppression
Prevent annotation for a specific function:
myMod.controller("Ctrl", /* @ngNoInject */ function ($scope) {});
// NOT annotated
function foo($scope) {
"ngNoInject";
}
// NOT annotated even if referenced⚙️ Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| explicitOnly | boolean | false | Only annotate functions with explicit @ngInject or "ngInject" markers |
| include | string[] | [".js", ".ts", ".mjs", ".cjs"] | File extensions to process |
| exclude | string[] | [".spec.js", ".spec.ts", ".test.js", ".test.ts"] | File extensions to exclude |
🧪 Programmatic API
The annotation engine can be used directly without the Vite plugin:
import { annotate } from "vite-ng-annotate";
const result = annotate('myMod.controller("Ctrl", function($scope) {});');
// 'myMod.controller("Ctrl", ["$scope", function($scope) {}]);'🛠️ Development
pnpm install
pnpm test # 🧪 Run tests
pnpm run coverage # 📊 Run tests with coverage
pnpm run lint # 🔍 Lint with oxlint
pnpm run format # 🎨 Format with oxfmt
pnpm run build # 📦 Build with tsc🪝 Git hooks
Husky runs the following hooks automatically:
- pre-commit — lint and format staged files via lint-staged (oxlint + oxfmt)
- commit-msg — validate commit messages with commitlint using Conventional Commits
feat: add new annotation pattern ✅
fix: handle edge case in parser ✅
updated stuff ❌🚢 Versioning & Release
This project uses Changesets for version management.
pnpm changeset # 📝 Create a new changeset
pnpm version # 🔖 Bump versions from pending changesets
pnpm release # 🚀 Build and publish to npmWorkflow
- Make your changes and create a changeset with
pnpm changeset - Select the semver bump type (patch / minor / major) and describe the change
- Commit the changeset file along with your changes
- When merged to
main, the CI will open a Version Packages PR - Merging that PR triggers an automated publish to npm
📄 License
MIT
