前言
公司项目的前端使用Ionic开发,但是代码的组织方式略显臃肿,代码的书写规范性不佳。一方面造成了严重的性能瓶颈,另一方面也无法应对未来频繁的需求变更,所以准备结合构建工具对前端做设计重构。考察了各个构建工具包括Glup、Grunt、Webpack之后还是决定使用Webpack(最新的主流一般都不会有错的)。查阅了不少资料,大部分是关注的技术细节,对于如何进行代码的组织设计较少,所以本文更多的关注的可能是代码的组织方式,探索一个比较好的组织方案
预备知识
- AMD/CMD
- npm
- AngularJS
- Webpack
Webpack
Webpack通过一个入口文件(当然可以多入口,不是重点),将所有依赖文件打包到一个文件中,从而减少SAP应用初次加载时http请求到个数。并且可以通过各种loader去处理优化不同的文件,如js、css、svg等。一般一个通行的做法是将webpack构建时的配置信息写入到一个名为 “webpack.config.js”的文件中。这部分的基础知识以及demo一般稍作学习便可掌握。官网文档本身很不错。
所以目录可以这样组织:
- app:存放源代码
- build: 存放webpack构建完成的代码
- node_modules: npm管理的包库
- webpack.config.js: webpack构建配置
这个是webpack视角下大的框架
组织结构
1. Module
关于module,angular的官方Developer Guide是这样写的:
You can think of a module as a container for the different parts of your app – controllers, services, filters, directives, etc.
所以在angular中module是一个层次相对较高的聚合,把相关的controller、service等封装到一个module,以实现一组特定的相对完备的功能。
对一个中大型的项目理想情况下是拆分为几个相对比较独立的module。
以passport模块为例,包含登录注册等功能,组织形式如下:
- index.js:将该模块内各个部分联系在一起
angular = require('angular');
uirouter = require('uirouter');
service = require('../services');
//define the app.passport module
module.exports = angular.module('app.passport', [uirouter, service.AuthService, service.Request])
.config(require('./passport.routes'))
.controller('SigninController', require('./signin.controller'))
.controller('SignupController', require('./signup.controller'))
.service('passportService', require('./passport.service'))
.name;
- routes.js:定义该模块的路由
module.exports = ['$stateProvider',
function($stateProvider) {
$stateProvider
.state('signup', {
url: '/signup',
name: 'signup',
template: require('./signup.html'),
controller: 'SignupController'
})
.state('signin', {
url: '/signin',
name: 'signin',
template: require('./signin.html'),
controller: 'SigninController'
});
}];
- signin.controller.js: 登录控制器,这里export controller的定义函数,在index中完成最终定义
module.exports = ['$scope','authService','request','$state',
function($scope, authService,request,$state) {
$scope.user = {
phone : "",
password:""
};
//执行用户登录操作
$scope.signin = function() {
request.post('signin', $scope.user)
.then(
function(data) {
authService.setToken(data.token, data.expire_in * 1000);
$state.go('home');
},
function(error) {
console.log(error);
}
);
};
}];
- signin.html 登录页面模版
主要思想就是对于模块内的无论是controller、config也好,将它们的定义放到单独文件中,通过index.js文件,将它们组装定义成目标module
** * 这边的定义都采用数组的方式进行,是为了以后进行代码混淆,函数的参数名即使被替换,依然不影响正常的inject * **
通过这种模式我们就完成了一个模块的定义。
2.项目
完成了一个模块的定义,那么整个项目的integration也就比较明了了。大致结构如下图:
首先是各个模块的定义,然后将跨模块的复用代码以service的形式提取出来,作为通用模块。这里我是放置在services目录下。整合时,定义app module作为项目的主模块,然后将其他module作为依赖的方式注入到app module中,从而完成整合。
- app.module.js
angular = require('angular');
uirouter = require('uirouter');
ionic = require('ionic');
ngCache = require('angular-cache');
config = require('./app.config');
home = require('./home');
passport = require('./passport');
angular.module('app', [ionic,uirouter, ngCache,home,passport])
.config(config.routing)
.config(config.providerConfig)
.constant('ENV', require('./app.env'));
- app.ENV.js 环境配置
module.exports = {
version: 1.0,
api: 'http://xxx.com/v1/',
appPath: 'http://localhost:8080/'
};
- app.config.js 配置信息
module.exports.providerConfig = ['$httpProvider', 'CacheFactoryProvider',
function($httpProvider, CacheFactoryProvider) {
var param = function(obj) {
var query = '',
name, value, fullSubName, subName, subValue, innerObj, i;
for (name in obj) {
value = obj[name];
if (value instanceof Array) {
for (i = 0; i < value.length; ++i) {
subValue = value[i];
fullSubName = name + '[' + i + ']';
innerObj = {};
innerObj[fullSubName] = subValue;
query += param(innerObj) + '&';
}
} else if (value instanceof Object) {
for (subName in value) {
subValue = value[subName];
fullSubName = name + '[' + subName + ']';
innerObj = {};
innerObj[fullSubName] = subValue;
query += param(innerObj) + '&';
}
} else if (value !== undefined && value !== null) query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&';
}
return query.length ? query.substr(0, query.length - 1) : query;
};
$httpProvider.defaults.transformRequest = function(obj) {
return angular.isObject(obj) && String(obj) !== '[object File]' ? param(obj) : obj;
};
$httpProvider.defaults.headers.post = {
'Content-Type': 'application/x-www-form-urlencoded'
}
$httpProvider.defaults.headers.put = {
'Content-Type': 'application/x-www-form-urlencoded'
}
angular.extend(CacheFactoryProvider.defaults, {
maxAge: 15 * 60 * 1000,
});
}
];
module.exports.routing = ['$urlRouterProvider', '$locationProvider',
function($urlRouterProvider, $locationProvider) {
$locationProvider.html5Mode(true);
$urlRouterProvider.otherwise('/signin');
}
];
More
这篇文章主要讲个大致思路,项目webpack-angular的完整代码已经放到github上面,有需要可以参考。代码的组织方式本身就是仁者见仁,智者见智,没有最好一说,所以还是希望可以一起探讨。