去年下半年接觸前端開發和AngularJS,一路跌跌撞撞,尤以模塊化代碼組織的教訓最深刻。
最開始代碼量小也沒經驗,直接在html引用所有js文件。后來搞得html文件越來越大,js的順序一亂,就報奇奇怪怪的錯誤,找半天。
第二個項目,我就想著要改進,直奔聲名顯赫的RequireJS去了。但RequireJS真的很麻煩有木有!平添了許多代碼,和AngularJS的Dependency Injection長得還挺像,一不小心就搞混。時不時報個Script Error出來,根本不知道問題在哪里。
有沒有更簡單的方法呢?正想著呢,ng-newsletter一聲炮響,送來了這篇文章。作者Jeff Dickey介紹了用模塊來組織代碼的四種方法,依照推薦程度,依次為:
- Require.js (Implementation of AMD)
- Browserify (Implementation of CommonJS)
- Angular dependency injection
- ES6 modules
作者著重寫了在第三種基礎上,組織代碼的簡化方法(捂臉,我才用到第一種)。
模塊的功用,首先是要隔離命名空間。Angular的模塊機制很好地解決了。除了angular這個對象本身,沒有任何暴露在Global里的對象。
然后是處理依賴關系。Angular的模塊同樣指定了依賴關系,而Dependency Injection在更細的粒度上做了類似的事。
更棒的是,Angular模塊的定義順序可以隨便。各個service、controller、directive注冊到模塊的順序,也沒有限制。唯一的要求只是:模塊要先定義,后使用。請看下面的例子:
// 模塊可以按任意的順序定義
angular.module('app', ['ctrl']);
angular.module('ctrl', ['svc']);
angular.module('svc', []);
// 使用尚未注冊的GithubSvc
angular.module('ctrl')
.controller('GithubCtrl', function ($scope, GithubSvc) {
GithubSvc.fetchStories().success(function (users) {
$scope.users = users;
});
});
angular.module('svc')
.factory('GithubSvc', function ($http) {
return {
fetchStories: function () {
return $http.get('https://api.github.com/users');
}
};
});
可運行的代碼在這個jsFiddle。
Angular沒有幫我們解決的,就剩下文件加載了。既然加載順序無關緊要,那直接把文件合并起來不就好了?反正最終都要合并的。
gulp.task('js', function () {
gulp.src(['src/**/module.js', 'src/**/*.js'])
.pipe(concat('app.js'))
.pipe(gulp.dest('.'))
})
無論是按業務還是按功能劃分目錄,只要把每個模塊的定義都放在目錄下的module.js
里,保證所有模塊在使用之前都已經定義過了,直接concat毫無問題。html里卻只有一行:
<script src="app.js"></script>
編寫、測試還是按模塊分目錄和文件,代碼組織清晰利落。拼接到一起在瀏覽器上跑。發布時,再ngAnnotate
和uglify
一下就好。
庫的代碼,再拼出一個vender.js
就行了。gulp-bower-files
這個插件,可以分析bower.json
,把依賴的每個庫里的main屬性組合起來,創建gulp.src()
。
gulp.task('vendor', function () {
gulpBowerFiles()
.pipe(concat('vendor.js'))
.pipe(gulp.dest('.'))
})
每次手工gulp js
不方便?gulp watch
起來。
gulp.task('watch', ['js'], function () {
gulp.watch('src/**/*.js', ['js'])
})
當然,這些都是權宜之計,因為傳說中的ES6和Angular 2.0,將搞定這些問題。不過在此之前,不妨試試這個超簡單的方法咯。
[補充]
-
gulp-bower-files
已被main-bower-files
取代。 -
module
在config
階段是有順序依賴的。比如,要定義route
里的controller
,就要求該controller
必須存在。